std::execution
Prerequisites
1. What is std::execution? (C++17)
C++17 introduced execution policies through the <execution> header.
They allow standard algorithms (such as std::for_each, std::transform, std::sort) to control how the algorithm is executed:
- sequentially
- in parallel
- or in a more aggressively optimized manner (parallel + vectorized)
2. Why std::execution exists?
Traditional STL algorithms define what to do, but not how to execute it.
1
| std::for_each(v.begin(), v.end(), f);
|
- Always sequential
- Single thread
- No control over execution strategy
With execution policies:
1
| std::for_each(std::execution::par, v.begin(), v.end(), f);
|
You explicitly tell the algorithm:
“You are allowed to run this in parallel.”
3. Available Execution Policies
| Policy | Description |
|---|
std::execution::seq | Sequential execution |
std::execution::par | Parallel (multi-threaded) execution |
std::execution::par_unseq | Parallel + unsequenced (SIMD + reordering allowed) |
| Feature | seq | par | par_unseq |
|---|
| Threads | 1 | Multiple | Multiple |
| SIMD | No | Optional | Yes (allowed) |
| Order Guarantee | Yes | No | No |
| Reordering | No | Limited | Fully allowed |
| Determinism | Yes | No | No |
3-1. std::execution::seq
1
| std::for_each(std::execution::seq, v.begin(), v.end(), f);
|
- Executes in order
- Single thread
- Same as default behavior
Characteristics
- deterministic
- easiest to debug
- no concurrency
3-2. std::execution::par
1
| std::for_each(std::execution::par, v.begin(), v.end(), f);
|
- Executes using multiple threads
- Work is split into chunks
- Order is not guaranteed
Characteristics
- multi-threaded
- faster for large workloads
- requires thread-safe operations
3-3. std::execution::par_unseq
1
| std::for_each(std::execution::par_unseq, v.begin(), v.end(), f);
|
- Parallel + vectorized execution
- Allows compiler to reorder operations
- Enables SIMD optimizations (automatically create SIMD operation on compiler)
Characteristics
- maximum performance potential
- no ordering guarantees at all
- strongest constraints on safety
3-4. std::execution::par vs std::execution::par_unseq
1
2
3
| std::for_each(std::execution::par, v.begin(), v.end(), [](int x) {
work(x);
});
|
std::execution::par
thread A: work(v[0]) thread B: work(v[5]) thread C: work(v[2])
std::execution::par_unseq
thread A: create SIMD - < v[0], v[1], v[2], v[3] >
4. Critical Rules
No Data Races
1
2
3
4
5
6
| int sum = 0;
std::for_each(std::execution::par, v.begin(), v.end(), [&](int x)
{
sum += x; // ❌ race condition
});
|
No Dependency Between Elements
1
2
| // ❌ depends on previous element
v[i] = v[i - 1] + 1;
|
Avoid Shared Mutable State
1
2
3
| // ❌ unsafe shared state
static int counter = 0;
counter++;
|