memcpy 性能不佳

Poor memcpy performance

我正在尝试优化一些代码以提高速度,并且它会花费大量时间来执行 memcpys。我决定编写一个简单的测试程序来单独测量 memcpy,看看我的内存传输速度有多快,但它们对我来说似乎很慢。我想知道是什么原因造成的。这是我的测试代码:

#include <stdio.h>
#include <string.h>
#include <time.h>
#include <stdlib.h>

#define MEMBYTES 1000000000

int main() {
  clock_t begin, end;
  double time_spent[2];
  int i;

  // Allocate memory                                                                                                                                    

  float *src = malloc(MEMBYTES);
  float *dst = malloc(MEMBYTES);


  // Fill the src array with some numbers                                                                                                               
  begin = clock();
  for(i=0;i<250000000;i++)
    src[i]=(float) i;
  end = clock();
  time_spent[0] = (double)(end - begin) / CLOCKS_PER_SEC;


  // Do the memcpy                                                                                                                                      
  begin = clock();
  memcpy(dst, src, MEMBYTES);
  end = clock();
  time_spent[1] = (double)(end - begin) / CLOCKS_PER_SEC;

  //Print results                                                                                                                                       
  printf("Time spent in fill: %1.10f\n", time_spent[0]);
  printf("Time spent in memcpy: %1.10f\n", time_spent[1]);
  printf("dst[200]: %f\n", dst[400]);
  printf("dst[200000000]: %f\n", dst[200000000]);

  //Free memory                                                                                                                                         
  free(src);
  free(dst);
}

/*                                                                                                                                                      
                                                                                                                                                        
  gcc -O3 -o mct memcpy_test.c                                                                                                                          
                                                                                                                                                        
*/

当我运行这个时,我得到以下输出:

Time spent in fill: 0.4263950000
Time spent in memcpy: 0.6350150000
dst[200]: 400.000000
dst[200000000]: 200000000.000000

我认为现代机器的理论内存带宽是几十 GB/s 或者可能超过 100 GB/s。我知道在实践中人们不能指望达到理论极限,并且对于大内存传输,事情可能会很慢,但我看到有人报告大传输的测量速度为 ~20GB/s(例如 here ).我的结果表明我得到 3.14GB/s(编辑:我最初有 1.57,但在评论中明确指出我需要计算读取和写入)。我想知道是否有人有可能有帮助的想法或关于为什么我看到的性能如此低的想法。

我的机器有两个 CPUS,每个有 12 个物理内核(Intel(R) Xeon(R) Gold 6126 CPU @ 2.60GHz) 有 192GB 内存(我相信它是 12x16GB DDR4-2666) OS 是 Ubuntu 16.04.6 LTS

我的编译器是: gcc (Ubuntu 5.4.0-6ubuntu1~16.04.12) 5.4.0 20160609

更新

感谢所有宝贵的反馈,我现在正在使用线程实现并获得更好的性能。谢谢!

我在发帖前尝试过线程化,但结果很差(我认为),但正如下面所指出的,我应该确保我使用的是挂钟时间。现在我的24个线程的结果如下:

Time spent in fill: 0.4229530000
Time spent in memcpy (clock): 1.2897100000
Time spent in memcpy (gettimeofday): 0.0589750000

我也在使用具有较大 SetMemcpyCacheLimit 值的 asmlib A_memcpy。

饱和 RAM 并不像看起来那么简单。

首先,乍一看这是我们可以根据提供的数字计算出的表观吞吐量:

  • 填充:1 / 0.4263950000 = 2.34 GB/s(读取1GB);
  • Memcpy: 2 / 0.6350150000 = 3.15 GB/s(读取1GB,写入1GB)。

问题是 malloc 分配的页面没有映射到 Linux 系统上的物理内存中。的确,mallocvirtual memory, but the pages are only mapped in physical memory when a first touch is performed causing expensive page faults中保留了一些space。 AFAIK,加快此过程的唯一方法是 使用多个内核 预填充缓冲区并稍后重用它们

此外,由于架构限制(即延迟),Xeon 处理器的一个核心无法使 RAM 饱和。同样,解决这个问题的唯一方法是使用多核。

如果您尝试使用多核,那么基准测试提供的结果将令人惊讶,因为 clock 不测量 wall-clock 时间 但是CPU time(这是所有线程花费的时间总和)。您需要使用另一个功能。在 C 中,您可以使用 gettimeofday(这并不完美,因为它不是 单调的 ),但肯定会使用 good-enough 作为您的基准(相关 post: How can I measure CPU time and wall clock time on both Linux/Windows?)。在 C++ 中,您应该使用 std::steady_clock(与 std::system_clock 相反,它是单调的)。

此外,write-allocate cache policy on x86-64 platform force cache lines to be read when they are written. This means that to write 1 GB, you actually need to read 1 GB! That being said, x86-64 processors provide non-temporal store instructions that does not cause this issue (assuming your array is aligned properly and big enough). Compilers can use that but GCC and Clang generally does not. memcpy is already optimized to use non-temporal stores on most machines. For more information, please read How do non temporal instructions work?.

最后,您可以使用 OpenMP 和循环上的简单 #pragma omp parallel for 指令轻松并行化基准测试。请注意,还提供了一个 user-friendly 函数来正确计算 wall-clock 时间:omp_get_wtime。对于 memcpy,最好的当然是编写一个循环,通过(相对较大的)块并行执行 memcpy

有关此主题的更多信息,我建议您阅读著名的文档:What Every Programmer Should Know About Memory. Since the document is a bit old, you can check the updating information about this here. The document also describe additional important things to understand why you may still not succeed saturate the RAM with the above information. One critical topic is NUMA