C++STL(ExecutionPolicy)算法如何确定要使用多少并行线程



C++17通过使用可选的ExecutionPolicy参数(作为第一个参数),升级了69个STL算法以支持并行性。例如

std::sort(std::execution::par, begin(v), end(v));

我怀疑C++17标准故意没有说明如何实现多线程算法,让库编写人员来决定什么是最好的(并允许他们稍后改变主意)。尽管如此,我还是很想从高层了解并行STL算法的实现中考虑了哪些问题。

我脑海中的一些问题包括(但不限于!):

  • (C++应用程序)使用的最大线程数与CPU&还是机器上的GPU核心
  • 每个算法使用的线程数有什么不同?(每种算法在每种情况下都会使用相同数量的线程吗?)
  • 是否考虑过其他线程(在同一应用程序中)上的其他并行STL调用?(例如,如果一个线程调用std::for_each(par,…),它是否会使用更多/更少/相同的线程,这取决于std::sort(par…)是否已经在其他线程上运行?可能有线程池吗?)
  • 是否考虑了外部因素导致的核心繁忙程度?(例如,如果1个核心非常繁忙,比如分析SETI信号,C++应用程序会减少它使用的线程数量吗?)
  • 有些算法只使用CPU核心吗?还是只有GPU核心
  • 我怀疑实现会因库而异(编译器与编译器?),甚至有关这方面的细节也会很有趣

我意识到这些并行算法的目的是保护程序员不必担心这些细节。然而,任何能让我对图书馆电话内部发生的事情有一个高层次的了解的信息都将不胜感激。

到目前为止,这些问题中的大多数都无法用标准来回答。然而,据我所知,你的问题混淆了两个概念:

C1.并行算法的约束

C2.算法的执行

所有C++17并行STL都是关于C1的:它设置了指令和/或线程在并行计算中如何交织/转换的约束。另一方面,C2是关于标准化的,关键字是executor(稍后会详细介绍)。

对于C1,有3个标准策略(在std::execution::seqparpar_unseq中)对应于任务和指令并行性的每一个组合。例如,当执行整数累加时,可以使用par_unseq,因为顺序并不重要。然而,对于浮点运算,在加法不关联的情况下,seq至少可以更好地得到确定性结果。简而言之:策略对并行计算设置了约束,智能编译器可能会利用这些约束。

另一方面,一旦你有了并行算法及其约束(可能经过一些优化/转换),executor就会找到执行它的方法。有默认的执行器(例如CPU),或者你可以创建自己的执行器,然后,所有关于线程数、工作负载、处理单元等的配置都可以设置。

到目前为止,C1是标准中的,但不是C2,所以如果您将C1与兼容的编译器一起使用,您将无法指定您想要的执行配置文件,库实现将为您决定(可能通过扩展)。

因此,为了解决您的问题:

(关于前5个问题)根据定义,C++17并行STL库不定义任何计算,只定义数据依赖性,以便进行可能的数据流转换。所有这些问题都将(希望)由executor回答,您可以在这里看到当前的提案。它看起来像:

executor = get_executor();
sort( std::execution::par.on(executor), vec.begin(), vec.end());

你的一些问题已经在提案中明确了。

(对于第6个)有很多库已经实现了类似的概念(C++executor确实受到了其中一些概念的启发),AFAIK:hpx、Thrust或Boost.Computer。我不知道最后两个是如何实际实现的,但对于hpx,它们使用了轻量级线程,您可以配置执行配置文件。此外,上面C++17代码的预期(尚未标准化)语法基本上与hpx中的语法相同(深受hpx的启发)。

参考文献:

Bryce Adelstein-lelbach的C++17并行算法及其超越
  • ISO C++异构计算的未来
  • Michael Wong的主题演讲"C++执行者在未来的C++中实现异构计算">
  • C++的执行器——Detlef Vollmann的长篇故事
  • 预版本C++17草案告诉任何关于">如何实现多线程算法"的内容,这是真的。实现所有者自行决定如何做到这一点。例如,并行STL使用TBB作为线程后端,使用OpenMP作为矢量化后端。我想,要了解这个实现如何与您的机器匹配,您需要阅读特定于实现的文档

    最新更新