您已具备将并行性固定到程序部分的基础知识。 C++17 得到了很多(例如并行版本的 foreach、sort、find 和 friends、map_reduce、map、reduce、prefix_sum ...)请参阅 C++ Extensions for Parallelism
然后你有像延续这样的项目。想想std::future,但继续。实现这些的方法很少(boost 现在有一个很好的方法,因为 std 没有 next(...) 或 then(...) 方法,但最大的好处是不必等它做下一个任务
auto fut = async([]( ){..some work...} ).then( [](result_of_prev ){...more work} ).then... ;
fut.wait( );
后续任务之间缺乏同步很重要,因为任务/线程/...之间的通信会减慢并行程序的速度。
因此,基于任务的并行性非常好。使用任务调度程序,您只需传递任务并走开。他们可能有一些方法,比如信号量,来回传信息,但这不是强制性的。 Intel Thread Building Blocks 和 Microsoft Parallel Pattern Library 都有这方面的功能。
之后我们就有了 fork/join 模式。它并不意味着为每个任务创建 N 个线程。只是你有这些 N 个,理想情况下独立的事情要做(fork),并且当它们完成时在某处有一个同步点(join)。
auto semaphore = make_semaphore( num_tasks );
add_task( [&semaphore]( ) {...task1...; semaphore.notify( ); } );
add_task( [&semaphore]( ) {...task2...; semaphore.notify( ); } );
...
add_task( [&semaphore]( ) {...taskN...; semaphore.notify( ); } );
semaphore.wait( );
从上面你可以开始看到这是一个流程图的模式。未来是(A >> B >> C >> D),分叉连接是(A|B|C|D)。有了它,您可以将它们组合成一个图表。 (A1>>A2|B1>>B2>>B3|C1|D1>>D2>>(E1>>E2|F1)) 其中 A1>>A2 表示 A1 必须先于 A2 而 A|B 表示 A 和 B可以同时运行。速度较慢的部分位于图/子图的末尾。
目标是找到系统中不需要通信的独立部分。如上所述,并行算法几乎在所有情况下都比它们的顺序算法慢,直到工作负载变得足够高或大小变得足够大(假设通信不太健谈)。例如排序。在 4 核计算机上,您将获得大约 2.5 倍的性能,因为合并很麻烦,需要大量同步,并且在第一轮合并后不能工作所有核心。在 N 非常大的 GPU 上,可以使用效率较低的排序,比如 Bitonic,它最终会非常快,因为你有很多工作人员来完成工作,每个人都安静地做自己的事情。
减少通信的一些技巧包括,使用数组来获取结果,这样每个任务就不会尝试锁定对象来推送值。通常以后这些结果的减少会很快。
但是对于所有类型的并行性,速度慢来自于通信。减少它。