std::execution并不是一个异步库,而是一个异步框架,一个高层抽象,可以配合各种标准算法如for_each,sort等实现异步。标准库包含下面几种策略:
std::execution::par或std::execution::par_unseq允许算法并行或向量化并行,实测调用TBB实现
std::execution::seq: std::execution::sequenced_policy 串行执行- 当你的算法需要元素顺序处理,或者你的数据集很小,以至于并行化不会带来性能提升时,使用顺序执行策略。
- 在单核处理器上,顺序执行通常是最佳选择,因为它避免了线程创建和管理的开销。
std::execution::unseq: std::execution::unsequenced_policy (c++20)- 当你的算法不需要保持元素处理的顺序,并且可以从向量化操作中受益时,使用无序执行策略。
- 这种策略适用于数值计算密集型的操作,如向量化的数学运算。
std::execution::par: std::execution::parallel_policy 多线程并行执行,如果采用多线程方式的话,每一个线程内的执行是顺序的、但是顺序不确定- 当你的算法可以并行执行,且数据集足够大,可以在多个核心上同时处理时,使用并行执行策略。
- 在多核处理器上,如果你的算法可以有效地分割工作负载,那么并行执行通常能提供性能提升。
std::execution::par_unseq: std::execution::parallel_unsequenced_policy 多个线程上并行,可以循环交叉,也能使用SIMD(单指令多数据)- 如果你的算法可以并行执行,并且结果不依赖于元素处理的顺序,使用并行无序执行策略。
- 这种策略允许算法在并行的同时进行向量化操作,可能在支持SIMD的硬件上提供最佳性能
在选择策略时,还应该考虑以下因素:
- 数据依赖性: 如果算法中的元素处理有依赖关系,那么并行化可能会变得复杂。在这种情况下,顺序执行可能是唯一的选择。
- 数据竞争: 在并行执行时,需要确保没有数据竞争。如果算法需要访问共享资源,可能需要使用同步机制,如互斥锁或原子操作。
- 硬件特性: 考虑你的硬件配置,如CPU核心数、缓存大小和SIMD支持。这些因素都会影响并行执行策略的性能。
- 编译器支持: 不同的编译器对C++并行STL的支持程度不同。确保你的编译器支持你想要使用的策略。
- 性能测试: 在实际部署之前,进行性能测试来比较不同策略的性能。这可以帮助你找到最适合你特定应用的策略。对于简单的处理函数,并行化带来的开销有可能覆盖并行化带来的加速,结果造成并行化后速度比串行慢,对于简单的处理函数或者处理数量较少的场景,并不需要进行并行处理。
平方计算参考耗时评估
并行处理的耗时波动会较串行高
1000个数据
--------------------------------------------------------------- Benchmark Time CPU Iterations --------------------------------------------------------------- BM_ForLoop 137 ns 137 ns 5250635 BM_ExecutionSeq 175 ns 175 ns 3940694 BM_ExecutionUnSeq 179 ns 179 ns 4114508 BM_ExecutionPar 16671 ns 15364 ns 50269 BM_ExecutionParUnseq 16184 ns 14997 ns 5360510000个数据
--------------------------------------------------------------- Benchmark Time CPU Iterations --------------------------------------------------------------- BM_ForLoop 1524 ns 1524 ns 445771 BM_ExecutionSeq 1599 ns 1599 ns 434946 BM_ExecutionUnSeq 1515 ns 1515 ns 476937 BM_ExecutionPar 14072 ns 13471 ns 53412 BM_ExecutionParUnseq 15050 ns 14580 ns 53740100000个数据
--------------------------------------------------------------- Benchmark Time CPU Iterations --------------------------------------------------------------- BM_ForLoop 26583 ns 26568 ns 19947 BM_ExecutionSeq 29984 ns 29982 ns 22200 BM_ExecutionUnSeq 32226 ns 32225 ns 20627 BM_ExecutionPar 37694 ns 36057 ns 21860 BM_ExecutionParUnseq 35214 ns 33818 ns 209351000000个数据
--------------------------------------------------------------- Benchmark Time CPU Iterations --------------------------------------------------------------- BM_ForLoop 572194 ns 572179 ns 1245 BM_ExecutionSeq 575447 ns 575434 ns 1189 BM_ExecutionUnSeq 648690 ns 648678 ns 1056 BM_ExecutionPar 253520 ns 233458 ns 2163 BM_ExecutionParUnseq 237059 ns 218291 ns 3805
sequenced_policy
std::execution::sequenced_policy用来指定算法应按顺序执行,即不进行并行化。如果未指定执行策略,则算法将按顺序执行。
| 优势 | 劣势 |
|---|---|
| 简单且可预测。避免数据争用。适用于小型任务,不存在并行开销。 | 对于大型任务效率不高。 |
注意:虽然 std::execution::sequenced_policy 指定了顺序执行,但它仍然允许算法实现使用任何同步机制来优化性能,只要这些优化不会改变操作的顺序,因此,即使指定了顺序执行策略,算法的实际执行仍然可能受到底层实现优化的影响,实测和for循环处理有差异,有几率比for更慢。
unsequenced_policy
std::execution::unsequenced_policy 是 C++20 标准中引入的执行策略,它表示算法的操作可以以非序的方式执行,但不一定并行。使用其作为算法的执行策略,通常是为了允许算法实现选择最优的执行方式,而不必担心操作的顺序。这种策略特别适用于那些不需要保持特定顺序,并且可以从任何执行顺序中受益的算法。此策略指定算法的执行可以向量化,即使用对多个数据项进行操作的指令在单个线程上执行。
| 优势 | 劣势 |
|---|---|
| 在单个线程上快速执行,避免竞争条件。 | 某些硬件可能不支持矢量化,非确定性执行序列。 |
parallel_policy
std::execution::parallel_policy用来指定算法应并行执行,即使用多个线程。但该标准没有指定应该使用的线程数。使用其作为算法的执行策略,通常是为了利用多核处理器或其他并行硬件来加速算法的执行。这种策略特别适用于那些可以并行化且没有严格顺序依赖关系的算法
| 优势 | 劣势 |
|---|---|
| 更快地执行大型任务,在多核系统下的效率最佳。 | 可能会引入开销。由于这种开销,可能并不总是比顺序执行快,也可能引入竞争。 |
parallel_unsequenced_policy
使用 std::execution::parallel_unsequenced_policy 通常是为了最大化并行性能,特别是在处理可以并行化且不需要保持特定顺序的任务时。这种策略允许算法实现选择任何合适的并行执行模型,包括使用SIMD(单指令多数据)指令集进行向量化执行。
| 优势 | 劣势 |
|---|---|
| 加快重复性操作的执行速度。 可以在带有矢量指令的硬件上使用。 | 不适合所有任务,并非所有硬件都支持。 |
参考
C++ Concurrency in Action (9/9)
C++ Execution:从零开始的 Sender/Receiver
Concurrency-with-Modern-Cpp学习笔记 - 标准库的并行算