In this section, we will write a program that uses some standard algorithms. The program itself is more of an example of how real-life scenarios can look than doing actual real-life work situation. While using these standard algorithms, we are embedding execution policies in order to speed the code up:
- First, we need to include some headers and declare that we use the std namespace. The execution header is a new one; it came with C++17:
#include <iostream>
#include <vector>
#include <random>
#include <algorithm>
#include <execution>
using namespace std;
- Just for the sake of the example, we'll declare a predicate function that tells whether a number is odd. We will use it later:
static bool odd(int n) { return n % 2; }
- Let's first define a large vector in our main function. We will fill it with a lot of data so that it takes some time to do calculations on it. The execution speed of this code will vary a lot, depending on the computer this code is executed on. Smaller/larger vector sizes might be better on different computers:
int main()
{
vector<int> d (50000000);
- In order to get a lot of random data for the vector, let's instantiate a random number generator along with a distribution and pack them up in a callable object. If this looks strange to you, please first have a look at the recipes that deal with random number generators and distributions in Chapter 25, Utility Classes:
mt19937 gen;
uniform_int_distribution<int> dis(0, 100000);
auto rand_num ([=] () mutable { return dis(gen); });
- Now, let's use the std::generate algorithm to fill the vector with random data. There is a new C++17 version of this algorithm, which can take a new kind of argument: an execution policy. We put in std::par here, which allows for automatic parallelization of this code. By doing this, we allow for multiple threads to start filling the vector together, which reduces the execution time if the computer has more than one CPU, which is usually the case with modern computers:
generate(execution::par, begin(d), end(d), rand_num);
- The std::sort method should also already be familiar. The C++17 version does also support an additional argument defining the execution policy:
sort(execution::par, begin(d), end(d));
- The same applies to std::reverse:
reverse(execution::par, begin(d), end(d));
- Then we use std::count_if to count all the odd numbers in the vector. And we can even parallelize that by just adding an execution policy again!
auto odds (count_if(execution::par, begin(d), end(d), odd));
- This whole program did not do any real scientific work, as we were just going to have a look on how to parallelize standard algorithms, but let's print something in the end:
cout << (100.0 * odds / d.size())
<< "% of the numbers are odd.n";
}
- Compiling and running the program gives us the following output. At this point, it is interesting to see how the execution speed differs when using the algorithms without an execution policy compared with all the other execution policies. Doing this is left as an exercise for the reader. Try it; the available execution policies are seq, par, and par_vec. We should get different execution times for each of them:
$ ./auto_parallel
50.4% of the numbers are odd.