6

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

// Compile with: gcc scaling.c -std=c99 -fopenmp -O3                                                                                               

#include <stdio.h>
#include <stdint.h>

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.9 秒后完成,因此它与使用单个程序实例运行 48 个线程完全相同。

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

4

3 回答 3

3

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

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

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

我没有足够的 AMD 知识来详细介绍,但据我所知,每个模块都有一个浮点单元(但有两个整数单元)。因此,您不能真正期望线性加速超过用于浮点运算的 Intel 内核或 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.) Intel Turbo Boost 和 AMD Turbo Core。

英特尔有一项称为 Turbo Boost 的技术,它可以根据正在运行的线程数改变时钟频率。当所有线程都在运行时,涡轮增压处于最低值。在 Linux 上,我知道的唯一可以在操作运行时测量这一点的应用程序是 powertop。获得真正的运行频率并不是一件容易测量的事情(因为它需要 root 访问权限)。在 Windows 上,您可以使用 CPUz。无论如何,结果是,与运行最大数量的真实内核相比,仅运行一个线程时您不能期望线性扩展。

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

于 2013-11-05T14:41:38.533 回答
3

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

在我的任何项目中,我从未注意到与线程数相比具有特别线性的性能。一方面,在我看来,调度程序绝不是严格公平的。OpenMP 一开始可能会在其线程组中平均分配任务,然后加入每个线程。在我有幸使用过的每个 Linux 机器上,我都希望有几个线程会提前完成,而几个线程会滞后。其他平台会有所不同。然而,这行得通,当然你在等待最慢的赶上。所以随机地说,线程处理的脉冲以钟形曲线的形式通过,线程越多,我认为越宽,直到后缘越过终点线,你永远不会完成。

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

使用这样的小循环,您可能不会遇到缓存抖动或任何此类烦恼。但另一个问题必然会降低一些性能:就像任何现代多核 CPU 一样,Opteron 在凉爽时以更高的时钟频率运行。您加热的核心越多,您看到的涡轮模式就越少。

于 2013-11-05T03:50:52.913 回答
2

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

对于我使用的动态时间表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 个线程的突然加速平台可以清楚地看出这一点;动态调度非常明显。从我选择的线程绑定中也很明显:事实证明,我上面写的对于绑定来说是一个糟糕的选择,因为你最终在每个“模块”中都有两个线程。

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

我不知道为什么“涡轮增压”的效果不明显(Redrum 答案的第 2 部分)。此外,我不能 100% 确定缩放的最后一点在哪里(可能是开销)丢失了(我们从模块数量的线性缩放中获得了 22 倍的性能,而不是预期的 24 倍)。但除此之外,这个问题得到了很好的回答。

于 2013-11-09T02:10:37.083 回答