【问题标题】:Performance: memset性能:memset
【发布时间】:2014-07-06 13:20:04
【问题描述】:

我有执行此操作的简单 C 代码(伪代码):

#define N 100000000
int *DataSrc = (int *) malloc(N);
int *DataDest = (int *) malloc(N);
memset(DataSrc, 0, N);
for (int i = 0 ; i < 4 ; i++) {
    StartTimer();
    memcpy(DataDest, DataSrc, N);
    StopTimer();
}
printf("%d\n", DataDest[RandomInteger]);

我的电脑:Intel Core i7-3930,配备 4x4GB DDR3 1600 内存,运行 RedHat 6.1 64 位。

第一个 memcpy() 以 1.9 GB/秒的速度出现,而接下来的三个以 6.2 GB/秒的速度出现。 缓冲区大小 (N) 太大,这不是由缓存效应引起的。所以,我的第一个问题:

  • 为什么第一个memcpy() 这么慢?也许malloc() 直到你使用它才完全分配内存?

如果我消除memset(),那么第一个memcpy() 的运行速度约为 1.5 GB/秒, 但接下来的三个以 11.8 GB/秒的速度运行。几乎 2 倍的加速。我的第二个问题:

  • 如果我不调用 memset(),为什么 memcpy() 会快 2 倍?

【问题讨论】:

  • 如果你从一个未初始化的源中 memcpy 不是 UB 吗?您使用什么编译器进行了哪些优化?通过将数据大小增加 10 倍或更多,使计时更可靠。
  • @usr 数据是随机的,没有ub,只要你不以可能引入ub的方式使用数据。示例中没有可以执行此操作的代码。
  • 顺便说一句:11.8GB/s 的总线速度对我来说似乎有点太快了。
  • @usr 读取未初始化的变量不会触发 ub,错误地使用该值会触发。例如,使用该值访问数组偏移量将触发 ub。我猜技术上(标准)你是对的。
  • 这可能是正确的,但 OP 特别提到了 gcc 和 linux。此外:int 没有可能的陷阱表示(并且从不使用 int,只复制)否则从未知磁盘文件中读取随机 dat 也可能导致问题。

标签: c performance memory-management


【解决方案1】:

正如其他人已经指出的那样,Linux 使用optimistic memory allocation strategy

第一个memcpys和后面的memcpys的区别在于DataDest的初始化。

正如您已经看到的,当您消除memset(DataSrc, 0, N) 时,第一个memcpy 会更慢,因为也必须分配源页面。当您同时初始化DataSrc DataDest 时,例如

memset(DataSrc, 0, N);
memset(DataDest, 0, N);

所有memcpys 将以大致相同的速度运行。

对于第二个问题:当您使用memset 初始化分配的内存时,所有页面将连续布局。另一方面,当您在复制时分配内存时,源页面和目标页面将交错分配,这可能会有所不同。

【讨论】:

  • 很棒的答案@Olaf Dietsche!
【解决方案2】:

这很可能是由于您的 VM 子系统中的延迟分配造成的。通常,当您分配大量内存时,只有前 N 页实际分配并连接到物理内存。当您访问超过前 N 个页面时,会生成页面错误,并且会根据“按需”分配和连接更多页面。

至于问题的第二部分,我相信一些虚拟机实现实际上会跟踪归零页面并专门处理它们。尝试将 DataSrc 初始化为实际(例如随机)值并重复测试。

【讨论】:

  • +1 - 'Dirtying'(写入)所有页面事先确实应该让事情变得清晰,可以尝试calloc()stackoverflow.com/q/1538420/1175253
  • @Sam:在我修复之前,该链接问题的最佳答案是不正确的;大多数主流操作系统上的calloc 从内核中获取零页面,因此它们仍然是延迟分配的,并且在读取或写入时会出现页面错误。
猜你喜欢
  • 2012-02-16
  • 1970-01-01
  • 2017-05-07
  • 2010-10-08
  • 1970-01-01
  • 2016-06-25
  • 1970-01-01
  • 1970-01-01
  • 2015-09-07
相关资源
最近更新 更多