为什么并行化会如此显着地降低性能?

Meh*_*dad 9 c++ parallel-processing performance multithreading

我有一个OpenMP程序(数千行,不可能在这里重现),其工作原理如下:

它由工作线程和任务队列组成.
任务包括卷积; 每当工作线程从工作队列中弹出一个任务时,它就会执行所需的卷积,并可选择将更多的卷积推送到队列中.
(没有特定的"主"线程;所有工人都是平等的.)

当我在自己的机器上运行这个程序(4核HT非NUMA Core i7)时,我得到的运行时间是:

(#threads: running time)
 1: 5374 ms
 2: 2830 ms
 3: 2147 ms
 4: 1723 ms
 5: 1379 ms
 6: 1281 ms
 7: 1217 ms
 8: 1179 ms
Run Code Online (Sandbox Code Playgroud)

这是有道理的.

但是,当我在NUMA 48核AMD Opteron 6168机器上运行时,我得到了这些运行时间:

 1: 9252 ms
 2: 5101 ms
 3: 3651 ms
 4: 2821 ms
 5: 2364 ms
 6: 2062 ms
 7: 1954 ms
 8: 1725 ms
 9: 1564 ms
10: 1513 ms
11: 1508 ms
12: 1796 ms  <------ why did it get worse?
13: 1718 ms
14: 1765 ms
15: 2799 ms  <------ why did it get *so much* worse?
16: 2189 ms
17: 3661 ms
18: 3967 ms
19: 4415 ms
20: 3089 ms
21: 5102 ms
22: 3761 ms
23: 5795 ms
24: 4202 ms
Run Code Online (Sandbox Code Playgroud)

这些结果非常一致,它不是机器上的负载神器.
所以我不明白:
在12核之后,什么可能导致性能下降如此之多?

我会明白,如果性能的饱和在一定程度上(我能责怪有限的内存带宽),但我不明白它如何能够下降,从1508毫秒,加入到5795毫秒更多的线程.

这怎么可能?

Mat*_*son 8

这种情况很难弄明白.一个关键是查看内存位置.如果没有看到你的代码,就不可能完全说出出了什么问题,但我们可以讨论一些让"多线程不那么好"的事情:

在所有NUMA系统中,当内存位于处理器X且代码在处理器Y上运行时(其中X和Y不是同一处理器),每次内存访问都会对性能造成不利影响.因此,在正确的NUMA节点上分配内存肯定会有所帮助.(这可能需要一些特殊的代码,例如设置亲和力掩码,并至少提示您希望Numa感知分配的OS /运行时系统).至少,确保您不是简单地处理由"第一个线程分配,然后启动更多线程"的一个大型数组.

另一件更糟糕的事情是共享或错误共享内存 - 所以如果两个或多个处理器使用相同的缓存行,那么你将在这两个处理器之间获得乒乓匹配,每个处理器将执行"我想要内存"在地址A",获取内存内容,更新它,然后下一个处理器将执行相同的操作.

结果在12个线程中变坏的事实似乎表明它与"套接字"有关 - 要么是共享数据,要么数据位于"错误的节点上".在12个线程中,您可能开始使用第二个套接字(更多),这将使这些问题更加明显.

为获得最佳性能,您需要在本地节点上分配内存,不需要共享,也不需要锁定.你的第一组结果看起来也不是"理想的".我有一些(绝对非共享)代码,它给处理器数量提供了n倍的好处,直到我用完处理器(不幸的是,我的机器只有4个内核,所以它不是很好,但它仍然好4倍超过1核心,如果我得到了48或64核机器,那么在计算"怪异数字"时会产生48或64个更好的结果.

编辑:

"套接字问题"是两件事:

  1. 内存位置:基本上,内存连接到每个套接字,因此如果内存是从属于"上一个"套接字的区域分配的,那么读取内存会有额外的延迟.

  2. 缓存/共享:在处理器内,存在共享数据的"快速"链接(通常是"底层共享缓存",例如L3缓存),这允许套接字内的核心比与其中的核心更有效地共享数据.一个不同的插座.

所有这些都类似于维修汽车,但你没有自己的工具箱,所以每次你需要一个工具时,你都要问你旁边的同事用螺丝刀,15毫米扳手,或者你需要的任何东西.然后在工作区域满员时返回工具.这不是一种非常有效的工作方式......如果你拥有自己的工具(至少是最常见的工具 - 你每月只使用一次的特殊扳手之一不是一个大问题,那就更好了,但你常见的10,12和15毫米扳手和一些螺丝刀,肯定).当然,如果有四种机制,共享相同的工具箱,情况会更糟.这是在四插槽系统中"在一个节点上分配所有内存"的情况.

现在想象你有一个"扳手盒",只有一个机械师可以使用扳手盒,所以如果你需要一个12毫米扳手,你必须等待你旁边的人完成使用15毫米扳手.如果您有"虚假缓存共享" - 处理器实际上没有使用相同的值,但由于缓存行中有多个"东西",处理器正在共享缓存行(缓冲区框),会发生这种情况.