C++ 并行计算的性能损失

C++ 并行计算的性能损失,c++,vectorization,c++17,C++,Vectorization,C++17,我有一个程序,或多或少会重复一些向量运算。当我尝试使用parallel\u来并行执行相同的任务时,我发现每个任务的时间显著增加。每个任务都从相同的数据中读取数据,并且没有进行同步。下面是示例代码(它需要Taskflow库(): 使用12项任务: # ./timing 12 Result: 1.0011e+12, Time: 788 Result: 1.0011e+12, Time: 609 Result: 1.0011e+12, Time: 812 Result: 1.0011e+12, Tim

我有一个程序,或多或少会重复一些向量运算。当我尝试使用
parallel\u来并行执行相同的任务时,我发现每个任务的时间显著增加。每个任务都从相同的数据中读取数据,并且没有进行同步。下面是示例代码(它需要Taskflow库():

使用12项任务:

# ./timing 12
Result: 1.0011e+12, Time: 788
Result: 1.0011e+12, Time: 609
Result: 1.0011e+12, Time: 812
Result: 1.0011e+12, Time: 605
Result: 1.0011e+12, Time: 808
Result: 1.0011e+12, Time: 1050
Result: 1.0011e+12, Time: 817
Result: 1.0011e+12, Time: 830
Result: 1.0011e+12, Time: 597
Result: 1.0011e+12, Time: 573
Result: 1.0011e+12, Time: 586
Result: 1.0011e+12, Time: 583
使用24项任务:

# ./timing 24
Result: 1.0011e+12, Time: 762
Result: 1.0011e+12, Time: 1033
Result: 1.0011e+12, Time: 735
Result: 1.0011e+12, Time: 1051
Result: 1.0011e+12, Time: 1060
Result: 1.0011e+12, Time: 757
Result: 1.0011e+12, Time: 1075
Result: 1.0011e+12, Time: 758
Result: 1.0011e+12, Time: 745
Result: 1.0011e+12, Time: 1165
Result: 1.0011e+12, Time: 1032
Result: 1.0011e+12, Time: 1160
Result: 1.0011e+12, Time: 757
Result: 1.0011e+12, Time: 743
Result: 1.0011e+12, Time: 736
Result: 1.0011e+12, Time: 1028
Result: 1.0011e+12, Time: 1109
Result: 1.0011e+12, Time: 1018
Result: 1.0011e+12, Time: 1338
Result: 1.0011e+12, Time: 743
Result: 1.0011e+12, Time: 1061
Result: 1.0011e+12, Time: 1046
Result: 1.0011e+12, Time: 1341
Result: 1.0011e+12, Time: 761
使用48项任务:

# ./timing 48
Result: 1.0011e+12, Time: 1591
Result: 1.0011e+12, Time: 1776
Result: 1.0011e+12, Time: 1923
Result: 1.0011e+12, Time: 1876
Result: 1.0011e+12, Time: 2002
Result: 1.0011e+12, Time: 1649
Result: 1.0011e+12, Time: 1955
Result: 1.0011e+12, Time: 1728
Result: 1.0011e+12, Time: 1632
Result: 1.0011e+12, Time: 1418
Result: 1.0011e+12, Time: 1904
Result: 1.0011e+12, Time: 1847
Result: 1.0011e+12, Time: 1595
Result: 1.0011e+12, Time: 1910
Result: 1.0011e+12, Time: 1530
Result: 1.0011e+12, Time: 1824
Result: 1.0011e+12, Time: 1588
Result: 1.0011e+12, Time: 1656
Result: 1.0011e+12, Time: 1876
Result: 1.0011e+12, Time: 1683
Result: 1.0011e+12, Time: 1403
Result: 1.0011e+12, Time: 1730
Result: 1.0011e+12, Time: 1476
Result: 1.0011e+12, Time: 1938
Result: 1.0011e+12, Time: 1429
Result: 1.0011e+12, Time: 1888
Result: 1.0011e+12, Time: 1530
Result: 1.0011e+12, Time: 1754
Result: 1.0011e+12, Time: 1794
Result: 1.0011e+12, Time: 1935
Result: 1.0011e+12, Time: 1757
Result: 1.0011e+12, Time: 1572
Result: 1.0011e+12, Time: 1474
Result: 1.0011e+12, Time: 1609
Result: 1.0011e+12, Time: 1394
Result: 1.0011e+12, Time: 1655
Result: 1.0011e+12, Time: 1480
Result: 1.0011e+12, Time: 2061
Result: 1.0011e+12, Time: 2056
Result: 1.0011e+12, Time: 1598
Result: 1.0011e+12, Time: 1630
Result: 1.0011e+12, Time: 1623
Result: 1.0011e+12, Time: 2073
Result: 1.0011e+12, Time: 1395
Result: 1.0011e+12, Time: 1487
Result: 1.0011e+12, Time: 1854
Result: 1.0011e+12, Time: 1569
Result: 1.0011e+12, Time: 1530

这段代码有什么问题吗?矢量化是并行的一个问题吗?我可以使用perf或类似的工具获得更好的洞察力吗?

超线程的存在是因为线程(在现实世界的场景中)经常必须等待内存中的数据,在数据传输过程中物理核心基本上处于空闲状态。您的示例(以及CPU,例如通过预取)正在努力避免这种内存限制,因此通过饱和线程数,同一个内核上的任何两个超线程都在争夺其内存。请注意,CPU上每个内核周期只有3个整数向量ALU可用-调度程序可能会让它们都忙于一个线程的操作

对于1个线程或12个线程,您不会真正遇到这种争用。对于24个线程,只有当每个线程都调度到自己的物理核心时,您才能避免此问题,这可能不会发生(因此您开始看到更糟糕的计时)。对于48个核心,您肯定会遇到上述问题

正如哈罗德(harold)所提到的,您可能也被存储绑定(超线程对竞争的另一个资源).

您可能需要证明这一点,但我猜测,由于工作线程在加载和存储之间没有做很多计算工作,它们反而受到CPU从RAM加载数据的速度的限制。因此,您拥有的线程越多,它们之间的竞争就越激烈,彼此之间的内存带宽也就越有限idth.如英特尔公司的文件所述:

随着越来越多的线程或进程共享有限的缓存容量和内存带宽资源,线程应用程序的可扩展性可能会受到限制。随着线程的增加,内存密集型线程应用程序可能会出现内存带宽饱和。在这种情况下,线程应用程序不会按预期缩放,性能可能会降低。 … 对于任何并行应用程序,带宽饱和的明显症状是不可伸缩行为。

使用VTune之类的工具进行评测是确定瓶颈位置的唯一方法。VTune的特点是它可以在CPU硬件级别分析性能,作为Intel工具,它可以访问其他工具可能无法访问的性能计数器和洞察,从而在CPU看到瓶颈时显示瓶颈。对于AMD CPU,equivalent工具是。可能使用的其他工具包括(从)和(如果运行Windows),如果是从


在指令级分析性能瓶颈可能很有用。它是一个静态分析器,对给定英特尔体系结构的吞吐量、延迟和数据相关性进行理论分析。但是,估计不包括内存、缓存等方面的影响。有关更多信息,请参阅。

24线程的数字是多少s?可能只是英特尔HT的性能不佳。顺便说一句,您可能应该合并add/mul/max步骤并同时执行所有步骤,保存2/3的加载和几乎所有的存储-至少,如果这是一项实际任务,而不仅仅是一个用于测试的合成加载。您是否打算让编译器丢弃除一个外的所有矢量化loops?如果您查看(搜索
伪作业
以了解哪些代码行去了哪里)您可以看到,除了最顶层的向量化循环外,其他所有循环都被消除了-编译器知道所有版本的结果都是相同的,因此它只保留最快的一个。这是一个简化的示例。在实际任务中,会生成随机数(每个任务都有自己的生成器)因此,每个循环产生不同的结果。但是每个向量上都有一些加法和乘法等,我可以用这个简单的例子重现计时差异。@Max不过,您知道编译器同时抛出
串行循环
代码和
SSE2循环
代码,对吗?它识别出那些e效率低于(且结果与相同)
AVX循环
。我怀疑数据加载是否是瓶颈。这是你能想象到的最有利于缓存和预取的任务,在加载方面基本上没有任何争议。我认为关于存储有一些争论,但你是对的,详细的分析是唯一的解决方法e、 正如我所说的,你可能是对的,我只是在猜测。但是讨论中的处理器有~60GB/s的带宽(),如果我们在测试的大致范围内,每个CPU可以饱和6-7个线程。在有限的算法中也有类似的问题:,。分析是唯一确定的方法。
# ./timing 24
Result: 1.0011e+12, Time: 762
Result: 1.0011e+12, Time: 1033
Result: 1.0011e+12, Time: 735
Result: 1.0011e+12, Time: 1051
Result: 1.0011e+12, Time: 1060
Result: 1.0011e+12, Time: 757
Result: 1.0011e+12, Time: 1075
Result: 1.0011e+12, Time: 758
Result: 1.0011e+12, Time: 745
Result: 1.0011e+12, Time: 1165
Result: 1.0011e+12, Time: 1032
Result: 1.0011e+12, Time: 1160
Result: 1.0011e+12, Time: 757
Result: 1.0011e+12, Time: 743
Result: 1.0011e+12, Time: 736
Result: 1.0011e+12, Time: 1028
Result: 1.0011e+12, Time: 1109
Result: 1.0011e+12, Time: 1018
Result: 1.0011e+12, Time: 1338
Result: 1.0011e+12, Time: 743
Result: 1.0011e+12, Time: 1061
Result: 1.0011e+12, Time: 1046
Result: 1.0011e+12, Time: 1341
Result: 1.0011e+12, Time: 761
# ./timing 48
Result: 1.0011e+12, Time: 1591
Result: 1.0011e+12, Time: 1776
Result: 1.0011e+12, Time: 1923
Result: 1.0011e+12, Time: 1876
Result: 1.0011e+12, Time: 2002
Result: 1.0011e+12, Time: 1649
Result: 1.0011e+12, Time: 1955
Result: 1.0011e+12, Time: 1728
Result: 1.0011e+12, Time: 1632
Result: 1.0011e+12, Time: 1418
Result: 1.0011e+12, Time: 1904
Result: 1.0011e+12, Time: 1847
Result: 1.0011e+12, Time: 1595
Result: 1.0011e+12, Time: 1910
Result: 1.0011e+12, Time: 1530
Result: 1.0011e+12, Time: 1824
Result: 1.0011e+12, Time: 1588
Result: 1.0011e+12, Time: 1656
Result: 1.0011e+12, Time: 1876
Result: 1.0011e+12, Time: 1683
Result: 1.0011e+12, Time: 1403
Result: 1.0011e+12, Time: 1730
Result: 1.0011e+12, Time: 1476
Result: 1.0011e+12, Time: 1938
Result: 1.0011e+12, Time: 1429
Result: 1.0011e+12, Time: 1888
Result: 1.0011e+12, Time: 1530
Result: 1.0011e+12, Time: 1754
Result: 1.0011e+12, Time: 1794
Result: 1.0011e+12, Time: 1935
Result: 1.0011e+12, Time: 1757
Result: 1.0011e+12, Time: 1572
Result: 1.0011e+12, Time: 1474
Result: 1.0011e+12, Time: 1609
Result: 1.0011e+12, Time: 1394
Result: 1.0011e+12, Time: 1655
Result: 1.0011e+12, Time: 1480
Result: 1.0011e+12, Time: 2061
Result: 1.0011e+12, Time: 2056
Result: 1.0011e+12, Time: 1598
Result: 1.0011e+12, Time: 1630
Result: 1.0011e+12, Time: 1623
Result: 1.0011e+12, Time: 2073
Result: 1.0011e+12, Time: 1395
Result: 1.0011e+12, Time: 1487
Result: 1.0011e+12, Time: 1854
Result: 1.0011e+12, Time: 1569
Result: 1.0011e+12, Time: 1530