什么限制了这个简单的OpenMP程序的扩展?

我试图了解48核系统上的并行化限制(4xAMD Opteron 6348,2.8 Ghz,每个CPU 12个核心)。 我写了这个微小的OpenMP代码来测试加速,我认为这是最好的情况(任务是令人尴尬的并行):

// Compile with: gcc scaling.c -std=c99 -fopenmp -O3 #include  #include  int main(){ const uint64_t umin=1; const uint64_t umax=10000000000LL; double sum=0.; #pragma omp parallel for reduction(+:sum) for(uint64_t u=umin; u<umax; u++) sum+=1./u/u; printf("%e\n", sum); } 

我惊讶地发现缩放是高度非线性的。 使用48个线程运行代码需要大约2.9s,使用36个线程运行3.1s,使用24个线程运行3.7s,使用12个线程运行4.9s,使用1个线程运行代码需要57s。

不幸的是,我不得不说计算机上运行一个进程使用100%的一个核心,因此可能会影响它。 这不是我的过程,所以我无法结束它来测试差异,但不知何故,我怀疑这是在19到20倍的加速和理想的48倍加速之间的区别。

为了确保它不是OpenMP问题,我同时运行了两个程序副本,每个程序有24个线程(一个用umin = 1,umax = 5000000000,另一个用umin = 5000000000,umax = 10000000000)。 在这种情况下,程序的两个副本在2.9s之后完成,因此它与使用单个程序实例运行48个线程完全相同。

用这个简单的程序阻止线性缩放的是什么?

我不确定这是否有资格作为答案,但感觉不仅仅是评论,所以我们走了。

我从来没有注意到任何项目中线程数量的特别线性性能。 首先,在我看来,调度程序是严格公平的。 OpenMP可能在一开始就将任务平均分配给其线程团队,然后加入每个任务。 在我喜欢的每个Linux机器上,我希望有一些线程可以提前完成,还有一些线程可以延迟。 其他平台会有所不同。 然而,当然,你正在等待最慢的追赶。 所以随机说来,在一条钟形曲线上有一个穿线处理的脉冲,我应该思考的线越多,你就越不会做到,直到后缘越过终点线。

top说什么? 它是否告诉你你的进程在20个线程上获得2000%的CPU,在40时获得4000%的CPU? 我打赌它逐渐消失。 顺便说一句, htop通常显示进程总数,并为每个线程分隔行。 这可能很有趣。

有了这样的微小循环,你可能不会遇到缓存捶打或任何这样的烦恼。 但另一个问题必然会削弱一些性能:就像任何现代的多核CPU一样,Opteron在很酷的情况下以更高的时钟频率运行。 您加热的核心越多,您将看到的涡轮模式越少。

我有两个重点,两个为什么你的结果不是线性的。 第一个是关于英特尔超线程和AMD模块。 下一个是关于英特尔和AMD的turbo频率模式

1.)超线程和AMD模块/核心

太多人将模块中的英特尔超线程和AMD内核混淆为真正的内核,并期望线性加速。 具有超线程的英特尔处理器可以运行两倍于内核的超线程/硬件线程。 AMD也拥有自己的技术,其中基本单元被称为模块,每个模块都有AMD不诚实地称之为核心什么是模块,什么是核心 。 容易混淆的一个原因是,例如在具有超踩踏的窗口中使用Task Mangager,它显示了硬件线程的数量,但它表示CPU。 这是误导性的,因为它不是CPU核心的数量。

我没有足够的AMD知识进入细节,但据我所知,每个模块都有一个浮点单元(但是两个整数单元)。 因此,对于浮点运算,您无法真正期望线性加速超过英特尔核心或AMD模块的数量。

在您的情况下,Opteron 6348每个处理器有2个芯片,每个芯片有3个模块,每个模块为2个“核心”。 虽然这提供了12个核心,但实际上只有6个浮点单元。

我在我的单插槽Intel Xeon E5-1620 @ 3.6 GHz上运行了你的代码。 它有4个内核和超线程(因此有8个硬件线程)。 我明白了:

 1 threads: 156s 4 threads: 37s (156/4 = 39s) 8 threads: 30s (156/8 = 19.5s) 

请注意,对于4个线程,缩放几乎是线性的,但对于8个线程,超线程只能帮助一点(至少它有帮助)。 另一个奇怪的观察是我的单线程结果远低于你的(MSVC2013 64位发布模式)。 我希望更快的单线程常春藤网桥核心可以轻松胜过更慢的AMD打桩机核心。 这对我来说没有意义。

2.)英特尔Turbo Boost和AMD Turbo Core。

英特尔有一项名为Turbo Boost的技术,它根据正在运行的线程数改变时钟频率。 当所有线程都在运行时,涡轮增压器处于最低值。 在Linux上,我知道的唯一一个可以在运行操作时测量它的应用程序是powertop。 获得真正的工作频率并不容易衡量(对于需要root访问权限的人来说)。 在Windows上,您可以使用CPUz。 在任何情况下,结果是,与运行最大数量的实际核心相比,仅运行一个线程时不能指望线性扩展。

再一次,我对AMD处理器的经验不多,但据我所知,他们的技术被称为Turbo Core,我希望效果类似。 这就是在比较线程代码时,良好的基准测试禁用turbo频率模式(在BIOS中,如果可以)的原因。

我终于有机会使用完全卸载的系统对代码进行基准测试: 在此处输入图像描述

对于动态时间表,我使用了schedule(dynamic,1000000) 。 对于静态计划,我使用了默认值(在核心之间均匀)。 对于螺纹装订,我使用了export GOMP_CPU_AFFINITY="0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47"

此代码高度非线性扩展的主要原因是AMD称之为“内核”的实际上并不是独立内核。 这是redrum答案的第(1)部分。 从24个线程的突然加速平台上看,这在上图中清晰可见; 动态调度非常明显。 从我选择的线程绑定中也很明显:事实certificate我上面写的内容对于绑定来说是一个糟糕的选择,因为你最终在每个“模块”中都有两个线程。

第二大减速来自具有大量线程的静态调度。 最慢和最快的线程之间不可避免地存在不平衡,当使用默认静态调度将迭代划分为大块时,在运行时引入大的波动。 这部分答案来自Hristo的评论和Salt的回答。

我不知道为什么“Turbo Boost”的效果并不明显(Redrum回答的第2部分)。 此外,我不是100%肯定在哪里(可能是在开销中)缩放的最后一位丢失了(我们得到22x性能而不是预期的24x来自模块数量的线性缩放)。 但除此之外,问题得到了很好的回答。