C:动态分配数组的memcpy速度

时间:2012-09-03 16:16:56

标签: c performance malloc memcpy

我需要有关以下代码性能的帮助。它对两个任意大小的动态分配数组执行memcpy:

int main()
{
  double *a, *b;
  unsigned n = 10000000, i;
  a = malloc(n*sizeof(double));
  b = malloc(n*sizeof(double));
  for(i=0; i<n; i++) {
    a[i] = 1.0;
    /* b[i] = 0.0; */
  }

  tic();
  bzero(b, n*sizeof(double));
  toc("bzero1");

  tic();
  bzero(b, n*sizeof(double));
  toc("bzero2");

  tic();
  memcpy(b, a, n*sizeof(double));
  toc("memcpy");
}

tic / toc测量执行时间。

在我的电脑上,memcpy需要0.035秒(Linux,gcc版本4.4.6)。 如果我现在取消注释初始化目标数组b的行,则代码快三倍(!) - 0.011s。

我在使用循环而不是memcpy时观察到了类似的行为。通常我不关心这个,因为它足以在使用之前“初始化”内存。但是,我现在需要执行一个简单的内存复制,并尽可能快地完成。初始化数据需要写入例如0到内存,这是不必要的,需要时间。我想用所有可用的内存带宽执行内存复制。

这个问题有解决方法吗?或者它是否与Linux处理动态内存的方式相关(某种懒惰的页面分配?)并且无法解决?它在其他系统上是怎么回事?

编辑:使用gcc 4.6获得相同的结果。我用-O3编译。

修改: 谢谢大家的意见。我明白内存映射需要时间。我想我只是很难接受它需要这么长时间,比实际的内存访问时间长得多。代码已被修改为包含使用两个后续bzero调用的数组b初始化的基准。时间现在显示

bzero1 0.273981
bzero2 0.056803
memcpy 0.117934

显然,第一个bzero调用很多不仅仅是将零流送到内存 - 即内存映射和内存清零。另一方面,第二个bzero调用占用了memcpy所需的一半时间,这与预期完全一样 - 只写时间与读写时间。据我所知,由于操作系统安全原因,第二次bzero调用的开销必须在那里。剩下的呢?我不能以某种方式减少它,例如使用更大的内存页面?不同的内核设置?

我应该提一下,我在Ubuntu喘息声中运行它。

3 个答案:

答案 0 :(得分:10)

这可能是懒惰的页面分配,Linux只在第一次访问时映射页面。 IIRC在Linux的新块中的每个页面都是一个空白页面的写入时复制,并且您的分配足以要求新块。

如果你想解决它,你可以用4k的间隔写一个字节或一个字。这可能会使映射到RAM的虚拟地址比写入整个页面的速度快一些。

我不希望(最有效的解决方法是强制执行延迟内存映射)加上(复制)比仅仅(复制)快得多,而不需要初始化b。因此,除非有一个特定的原因让你关心副本的性能,而不是整个操作的性能,否则它是徒劳的。它是“立即付款或稍后付款”,Linux稍后付款,而您只是稍后测量时间。

答案 1 :(得分:6)

当然,如果您将初始化速度和复制速度与仅复制速度进行比较,则初始化应包含在定时部分中。在我看来你应该实际比较这个:

// Version 1
for(i=0; i<n; i++)
    a[i] = 1.0;

tic();

memcpy(b, a, n*sizeof(double));

toc();

对此:

// Version 2
for(i=0; i<n; i++)
    a[i] = 1.0;

tic();

for(i=0; i<n; i++)
    b[i] = 0.0;
memcpy(b, a, n*sizeof(double));

toc();

我预计这会使你的3倍速度提升急剧下降。

编辑:正如Steve Jessop所建议的,您可能还想测试第三种策略,即每页只触及一个条目:

// Version 3
for(i=0; i<n; i++)
    a[i] = 1.0;

tic();

for(i=0; i<n; i+=DOUBLES_PER_PAGE)
    b[i] = 0.0;
memcpy(b, a, n*sizeof(double));

toc();

答案 2 :(得分:5)

由于(1)延迟页面分配和(2)内核延迟页面零初始化,第一个bzero运行时间更长。虽然出于安全原因,第二个原因是不可避免的,但可以通过使用更大(“巨大”)的页面来优化延迟页面分配。

至少有两种方法可以在Linux上使用大页面。困难的方法是hugetlbfs。简单的方法是Transparent huge pages

在系统上的进程列表中搜索khugepaged。如果存在此类过程,则支持透明的大页面,如果您将malloc更改为此,则可以在应用程序中使用它们:

posix_memalign((void **)&b, 2*1024*1024, n*sizeof(double));
madvise((void *)b, n*sizeof(double), MADV_HUGEPAGE);