c - Memcpy 与 memset 耗时相同

标签 c linux memory x86 malloc

我想使用 memcpy 测量内存带宽。我修改了这个答案的代码:why vectorizing the loop does not have performance improvement它使用 memset 来测量带宽。问题是 memcpy 只比 memset 慢一点,我预计它会慢两倍左右,因为它在两倍的内存上运行。

更具体地说,我通过以下操作运行了超过 1 GB 的数组 ab(分配将 calloc)100 次。

operation             time(s)
-----------------------------
memset(a,0xff,LEN)    3.7
memcpy(a,b,LEN)       3.9
a[j] += b[j]          9.4
memcpy(a,b,LEN)       3.8

请注意 memcpy 仅比 memset 稍慢。操作 a[j] += b[j](其中 j 超过 [0,LEN))应该花费三倍于memcpy 因为它处理三倍多的数据。然而,它只比 memset 慢 2.5 倍。

然后我用 memset(b,0,LEN)b 初始化为零并再次测试:

operation             time(s)
-----------------------------
memcpy(a,b,LEN)       8.2
a[j] += b[j]          11.5

现在我们看到 memcpy 大约是 memset 的两倍,而 a[j] += b[j] 大约是三倍和我预期的一样慢 memset

至少在 memset(b,0,LEN) 之前,我预计 memcpy 会是 slower because the of lazy allocation (first touch)在 100 次迭代中的第一次迭代中。

为什么我只在 memset(b,0,LEN) 之后得到预期的时间?

测试.c

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

void tests(char *a, char *b, const int LEN){
    clock_t time0, time1;
    time0 = clock();
    for (int i = 0; i < 100; i++) memset(a,0xff,LEN);
    time1 = clock();
    printf("%f\n", (double)(time1 - time0) / CLOCKS_PER_SEC);

    time0 = clock();
    for (int i = 0; i < 100; i++) memcpy(a,b,LEN);
    time1 = clock();
    printf("%f\n", (double)(time1 - time0) / CLOCKS_PER_SEC);

    time0 = clock();
    for (int i = 0; i < 100; i++) for(int j=0; j<LEN; j++) a[j] += b[j];
    time1 = clock();
    printf("%f\n", (double)(time1 - time0) / CLOCKS_PER_SEC);

    time0 = clock();
    for (int i = 0; i < 100; i++) memcpy(a,b,LEN);
    time1 = clock();
    printf("%f\n", (double)(time1 - time0) / CLOCKS_PER_SEC);

    memset(b,0,LEN);
    time0 = clock();
    for (int i = 0; i < 100; i++) memcpy(a,b,LEN);
    time1 = clock();
    printf("%f\n", (double)(time1 - time0) / CLOCKS_PER_SEC);

    time0 = clock();
    for (int i = 0; i < 100; i++) for(int j=0; j<LEN; j++) a[j] += b[j];
    time1 = clock();
    printf("%f\n", (double)(time1 - time0) / CLOCKS_PER_SEC);
}

主.c

#include <stdlib.h>

int tests(char *a, char *b, const int LEN);

int main(void) {
    const int LEN = 1 << 30;    //  1GB
    char *a = (char*)calloc(LEN,1);
    char *b = (char*)calloc(LEN,1);
    tests(a, b, LEN);
}

使用 (gcc 6.2) gcc -O3 test.c main.c 编译。 Clang 3.8 给出了基本相同的结果。

测试系统:i7-6700HQ@2.60GHz (Skylake),32GB DDR4,Ubuntu 16.10。在我的 Haswell 系统上,带宽在 memset(b,0,LEN) 之前有意义,即我只在我的 Skylake 系统上看到问题。

我首先从 a[j] += b[k] 操作中发现了这个问题 in this answer这是高估了带宽。


我想到了一个更简单的测试

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

void __attribute__ ((noinline))  foo(char *a, char *b, const int LEN) {
  for (int i = 0; i < 100; i++) for(int j=0; j<LEN; j++) a[j] += b[j];
}

void tests(char *a, char *b, const int LEN) {
    foo(a, b, LEN);
    memset(b,0,LEN);
    foo(a, b, LEN);
}

这输出。

9.472976
12.728426

但是,如果我在 calloc 之后在 main 中执行 memset(b,1,LEN)(见下文),那么它会输出

12.5
12.5

这让我认为这是操作系统分配问题,而不是编译器问题。

#include <stdlib.h>

int tests(char *a, char *b, const int LEN);

int main(void) {
    const int LEN = 1 << 30;    //  1GB
    char *a = (char*)calloc(LEN,1);
    char *b = (char*)calloc(LEN,1);
    //GCC optimizes memset(b,0,LEN) away after calloc but Clang does not.
    memset(b,1,LEN);
    tests(a, b, LEN);
}

最佳答案

重点是 malloccalloc 在大多数平台上 不分配内存;他们分配地址空间

malloc 等工作方式:

  • 如果空闲列表可以满足请求,则从中切出一 block
    • calloc 的情况下:发出等同于 memset(ptr, 0, size)
  • 如果不是:要求操作系统扩展地址空间。

对于具有请求分页 (COW) 的系统(MMU 可以提供帮助),第二个选项归结为:

  • 为请求创建足够的页表条目,并用对 /dev/zero 的 (COW) 引用填充它们>
  • 添加这些 PTEs到进程的地址空间

除了页表之外,这不会消耗任何物理内存。

  • 一旦引用新内存进行读取,读取将来自/dev/zero/dev/zero 设备是一个非常特殊的设备,在本例中映射到新内存的每一页
  • 但是,如果写入了新页面,COW 逻辑就会启动(通过页面错误):
    • 分配物理内存
    • /dev/zero 页面被复制到新页面
    • 新页面与母页面分离
    • 调用进程最终可以进行更新,这一切都开始了

关于c - Memcpy 与 memset 耗时相同,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43168595/

相关文章:

c - 如何在 Visual Studio 中自动同​​步标题?

linux - 不能在内核模块 makefile 中使用通配符

c++ - 在没有手动分配缓冲区的情况下使用 sprintf

c - 如何从二进制文件读取和写入 float ?

c - 结构体和多维数组

c++ - 静态多维数组 (C/C++)

python - 是否有与 Borland C 命令 putpixel 等效的 Matplotlib?

javascript - 如何在 C++ 中执行 Javascript 函数

java - 使用带有 java 5 的 jmx 以编程方式获取堆信息

c - 为该数组保留的内存何时被释放?