繁体   English   中英

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

[英]Why would parallelization decrease performance so dramatically?

我有一个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

这是有道理的。

但是,当我在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

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

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

这怎么可能?

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

在所有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毫米扳手。 如果您有“虚假缓存共享” - 处理器实际上没有使用相同的值,但由于缓存行中有多个“东西”,处理器正在共享缓存行(缓冲区框),会发生这种情况。

我有两个建议:

1.)在NUMA系统上,您要确保写入的缓冲区与页面边界对齐,也是页面的倍数。 页面通常为4096字节。 如果在页面之间拆分缓冲区,则会出现错误共享。

http://dl.acm.org/citation.cfm?id=1295483

当共享存储器并行系统中的处理器在相同的相干块(高速缓存行或页面)内引用不同的数据对象时,发生错误共享,从而引起“不必要的”一致性操作。

和这个链接https://parasol.tamu.edu/~rwerger/Courses/689/spring2002/day-3-ParMemAlloc/papers/lee96effective.pdf

...当可能具有不同访问模式的几个独立对象被分配给相同的可移动存储器单元时发生的错误共享(在我们的例子中,是一个虚拟存储器页面)。

因此,例如,如果一个数组是5000字节,你应该使它成为8192字节(2 * 4096)。 然后用类似的东西将它对齐

float* array = (float*)_mm_malloc(8192, 4096);  //two pages both aligned to a page

在非NUMA系统上,您不希望多个线程写入同一缓存行(通常为64个字节)。 这会导致错误共享。 在NUMA系统上,您不希望多个线程写入同一页面(通常为4096字节)。

请参阅此处的一些注释在不使用临界区的情况下,与OpenMP并行填充直方图(数组缩减)

2.)OpenMP可以将线程迁移到不同的核心/处理器,因此您可能希望将线程绑定到某些核心/处理器。 您可以使用ICC和GCC执行此操作。 使用GCC我认为你想做一些像GOMP_CPU_AFFINITY=0 2 4...看到这个链接在这个简单的OpenMP程序中有什么限制扩展?

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM