多通道x86系统的内存带宽

86

我正在测试台式机和服务器的内存带宽。

Sklyake desktop 4 cores/8 hardware threads
Skylake server Xeon 8168 dual socket 48 cores (24 per socket) / 96 hardware threads

系统的峰值带宽为{{peak bandwidth}}。
Peak bandwidth desktop = 2-channels*8*2400 = 38.4 GB/s
Peak bandwidth server  = 6-channels*2-sockets*8*2666 = 255.94 GB/s

我正在使用自己的来自STREAM的三元函数来测量带宽(完整代码稍后提供)

void triad(double *a, double *b, double *c, double scalar, size_t n) {
  #pragma omp parallel for
  for(int i=0; i<n; i++) a[i] = b[i] + scalar*c[i];
}

这是我得到的结果

         Bandwidth (GB/s)
threads  Desktop  Server         
1             28      16
2(24)         29     146
4(48)         25     177
8(96)         24     189 

对于一个线程,我不理解为什么桌面比服务器快得多。根据这个答案 https://dev59.com/Eeo6XIcBkEYKwwoYLhbh#18159503 SSE足以获得双通道系统的全带宽,这就是我在桌面上观察到的。两个线程只能稍微有所帮助,而4和8个线程却会导致更差的结果。但是,在服务器上,单线程带宽要小得多。这是为什么? 在服务器上,使用96个线程可以获得最佳结果。我本以为很少的线程就可以使其饱和。为什么需要这么多线程才能使服务器带宽达到饱和?我的结果存在很大的误差范围,我没有包括误差估计。我选择了几次运行中的最佳结果。
代码
//gcc -O3 -march=native triad.c -fopenmp
//gcc -O3 -march=skylake-avx512 -mprefer-vector-width=512 triad.c -fopenmp
#include <stdio.h>
#include <omp.h>
#include <x86intrin.h>

void triad_init(double *a, double *b, double *c, double k, size_t n) {
  #pragma omp parallel for
  for(size_t i=0; i<n; i++) a[i] = k, b[i] = k, c[i] = k;
}

void triad(double *a, double *b, double *c, double scalar, size_t n) {
  #pragma omp parallel for
  for(size_t i=0; i<n; i++) a[i] = b[i] + scalar*c[i];
}

void triad_stream(double *a, double *b, double *c, double scalar, size_t n) {
#if defined ( __AVX512F__ ) || defined ( __AVX512__ )
  __m512d scalarv = _mm512_set1_pd(scalar);
  #pragma omp parallel for
  for(size_t i=0; i<n/8; i++) {
    __m512d bv = _mm512_load_pd(&b[8*i]), cv = _mm512_load_pd(&c[8*i]);
    _mm512_stream_pd(&a[8*i], _mm512_add_pd(bv, _mm512_mul_pd(scalarv, cv)));
  }        
#else
  __m256d scalarv = _mm256_set1_pd(scalar);
  #pragma omp parallel for
  for(size_t i=0; i<n/4; i++) {
    __m256d bv = _mm256_load_pd(&b[4*i]), cv = _mm256_load_pd(&c[4*i]);
    _mm256_stream_pd(&a[4*i], _mm256_add_pd(bv, _mm256_mul_pd(scalarv, cv)));
  }        
#endif
}

int main(void) {
  size_t n = 1LL << 31LL; 
  double *a = _mm_malloc(sizeof *a * n, 64), *b = _mm_malloc(sizeof *b * n, 64), *c = _mm_malloc(sizeof *c * n, 64);
  //double peak_bw = 2*8*2400*1E-3; // 2-channels*8-bits/byte*2400MHz
  double peak_bw = 2*6*8*2666*1E-3; // 2-sockets*6-channels*8-bits/byte*2666MHz
  double dtime, mem, bw;
  printf("peak bandwidth %.2f GB/s\n", peak_bw);

  triad_init(a, b, c, 3.14159, n);
  dtime = -omp_get_wtime();
  triad(a, b, c, 3.14159, n);  
  dtime += omp_get_wtime();
  mem = 4*sizeof(double)*n*1E-9, bw = mem/dtime;
  printf("triad:       %3.2f GB, %3.2f s, %8.2f GB/s, bw/peak_bw %8.2f %%\n", mem, dtime, bw, 100*bw/peak_bw);

  triad_init(a, b, c, 3.14159, n);
  dtime = -omp_get_wtime();
  triad_stream(a, b, c, 3.14159, n);  
  dtime += omp_get_wtime();
  mem = 3*sizeof(double)*n*1E-9, bw = mem/dtime;
  printf("triads:      %3.2f GB, %3.2f s, %8.2f GB/s, bw/peak_bw %8.2f %%\n", mem, dtime, bw, 100*bw/peak_bw);
}

16
Skylake服务器处理器和Skylake桌面处理器之间的一个重要区别在于核心之间的互连方式。桌面处理器采用环形总线互连,而服务器处理器则采用核心之间的网状网络。Broadwell服务器CPU也采用了环形总线,但对于更高的核心数来说这种解决方案不太可扩展。事实上,Skylake-SP的网状网络具有很强的可扩展性,但单线程内存带宽非常令人失望。 - wim
5
请参考这篇Skylake-SP内存子系统的描述测试结果,证实了单线程内存带宽较低。 - wim
2
@Zboson: 感谢夸奖。说实话,我认为我在内存子系统方面的专业知识还不够,无法给出决定性的答案。 我知道网状互连比环形总线更可扩展,但他们为什么不能设计一个网状互连,至少具有与Broadwell服务器CPU相同的单线程DRAM内存带宽呢?那会花费太多硅,或者太多功率(热量)吗? 我只能猜测。AVX-512实际上需要比AVX2更多的带宽,而不是更少。 - wim
3
请注意,有限的单线程带宽使得生产可扩展的计算结果更加容易。这让我想起了一个著名的论文《在并行计算机上给出性能结果时愚弄众人的12种方法》和Georg Hager的博客《愚弄众人》。 - wim
1
值得注意的是,许多服务器安装包括两个插槽而不是一个,因此从单个线程中,您可以访问近端和远端内存,因此后者中增加的访问延迟可能会降低执行的总吞吐量。如果我没记错的话,现代libc分配器意识到了局部性,但最好通过像hwloc或类似工具进行检查。 - Jorge Bellon
显示剩余15条评论
1个回答

4

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接