execution

Posted by     "LETTER" on Thursday, December 4, 2025

std::execution并不是一个异步库,而是一个异步框架,一个高层抽象,可以配合各种标准算法如for_eachsort等实现异步。标准库包含下面几种策略:

std::execution::parstd::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        53605
    
  • 10000个数据

    ---------------------------------------------------------------
    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        53740
    
  • 100000个数据

    ---------------------------------------------------------------
    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        20935
    
  • 1000000个数据

    ---------------------------------------------------------------
    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学习笔记 - 标准库的并行算

Modern C++中 STL 算法的执行策略