ang*_*nor 13 c malloc performance 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");
}
Run Code Online (Sandbox Code Playgroud)
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 wheeze上运行它.
Ste*_*sop 10
它可能是懒惰的页面分配,Linux只在第一次访问时映射页面.IIRC在Linux的新块中的每个页面都是一个空白页面的写入时复制,并且您的分配足以要求新块.
如果你想解决它,你可以用4k的间隔写一个字节或一个字.这可能会使映射到RAM的虚拟地址比写入整个页面的速度快一些.
我不希望(最有效的解决办法强制执行延迟内存映射)加上(复制)要比没有初始化的(复制)明显更快b,但是.因此,除非有一个特定的原因让你关心副本的性能,而不是整个操作的性能,否则它是徒劳的.它是"立即付款或稍后付款",Linux稍后付款,而您只是稍后测量时间.
当然,如果您将初始化速度和复制速度与仅复制速度进行比较,则初始化应包含在定时部分中.在我看来你应该实际比较这个:
// Version 1
for(i=0; i<n; i++)
a[i] = 1.0;
tic();
memcpy(b, a, n*sizeof(double));
toc();
Run Code Online (Sandbox Code Playgroud)
对此:
// 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();
Run Code Online (Sandbox Code Playgroud)
我预计这将使你的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();
Run Code Online (Sandbox Code Playgroud)
由于(1)延迟页面分配和(2)内核的惰性页面零初始化,第一个bzero运行得更长.虽然由于安全原因第二个原因是不可避免的,但是可以通过使用更大("巨大")的页面来优化延迟页面分配.
至少有两种方法可以在Linux上使用大页面.艰难的方式是hugetlbfs.简单的方法是透明的巨大页面.
搜索khugepaged系统上的进程列表.如果存在这样的过程,则支持透明的大页面,如果更改malloc为此,则可以在应用程序中使用它们:
posix_memalign((void **)&b, 2*1024*1024, n*sizeof(double));
madvise((void *)b, n*sizeof(double), MADV_HUGEPAGE);
Run Code Online (Sandbox Code Playgroud)