Easy multithreading with execution policies in C++
Introduction
Execution policies are a great way to easily improve the performance of your code by utilising all threads on a computer. Execution policies are part of the algorithms library and were added as part of the c++17 specification. They make use of the Threading Building Blocks library, which has been developed by Intel.
Execution policies in action
Consider the following example:
std::vector<int> vec;
vec.resize(10);
std::iota(vec.begin(), vec.end(), 1);
auto fn = [](int& i) {
sleep(1);
std::cout << i << '\n';
};
std::for_each(worlds.begin(), worlds.end(), fn);
std::cout.flush();
The function std::for_each()
will loop over the elements of a collection, the range of which is specified by the first two parameters, which in this case are addresses to the beginning and end of the collection respectively. The function pointer fn
is passed in as the third parameter and is supposed to be the body of the for-each loop. This code is single-threaded, but with a simple change this code can be made to run on multiple threads:
std::vector<int> vec;
vec.resize(10);
std::iota(vec.begin(), vec.end(), 1);
auto fn = [](int& i) {
sleep(1);
std::cout << i << '\n';
};
std::for_each(std::execution::par, worlds.begin(), worlds.end(), fn);
std::cout.flush();
In order to compile the above code, you will need to install the TBB library if you have not already. Then, if you are using g++, use this command to compile your code:
g++ -ltbb --std=c++17 main.cpp
The four types of execution policy
I will be using the code in the previous section to demonstrate how the execution policies differ.
std::execution::seq
This execution policy will iterate through the elements of the collection one at a time without multithreading. This results in the same behaviour as omitting the std::execution parameter.
time ./a.out
1
2
3
4
5
6
7
8
9
10
real 0m10.010s
user 0m0.003s
sys 0m0.003s
std::execution::par
This execution policy will iterate through the elements of the collection with multithreading. This means that elements of the collection may be evaluated out of order.
time ./a.out
1
2
3
4
6
8
5
9
7
10
real 0m2.007s
user 0m0.006s
sys 0m0.005s