为什么memmove比memcpy更快?
我正在调查一个应用程序中的性能热点,该应用程序将其时间的50%用于memmove(3)。 应用程序将数百万个4字节整数插入到已排序的数组中,并使用memmove将数据“向右移动”,以便为插入值腾出空间。
我的期望是复制记忆的速度非常快,我惊讶于花费了太多的时间。 但是后来我认为memmove很慢,因为它正在移动重叠的区域,这必须在紧密的循环中执行,而不是复制大页面的内存。 我写了一个小型的微型基准来发现memcpy和memmove之间是否存在性能差异,并期待memcpy赢得双手。
我在两台机器上运行我的基准测试(核心i5,核心i7),并且发现memmove实际上比memcpy更快,在旧版核心i7上甚至几乎快两倍! 现在我正在寻找解释。
这是我的基准。 它用memcpy复制100 MB,然后用memmove移动大约100 MB; 源和目的地重叠。 尝试了源和目标的各种“距离”。 每次测试运行10次,平均时间被打印。
https://gist.github.com/cruppstahl/78a57cdf937bca3d062c
这里是Core i5(Linux 3.5.0-54-generic#81〜precise1-Ubuntu SMP x86_64 GNU / Linux,gcc是4.6.3(Ubuntu / Linaro 4.6.3-1ubuntu5)的结果。括号中的数字是源和目标之间的距离(间隙大小):
memcpy 0.0140074
memmove (002) 0.0106168
memmove (004) 0.01065
memmove (008) 0.0107917
memmove (016) 0.0107319
memmove (032) 0.0106724
memmove (064) 0.0106821
memmove (128) 0.0110633
Memmove是作为SSE优化的汇编代码实现的,从后向前复制。 它使用硬件预取将数据加载到缓存中,并将128个字节复制到XMM寄存器,然后将它们存储在目标中。
(memcpy-ssse3-back.S,第1650行ff)
L(gobble_ll_loop):
prefetchnta -0x1c0(%rsi)
prefetchnta -0x280(%rsi)
prefetchnta -0x1c0(%rdi)
prefetchnta -0x280(%rdi)
sub $0x80, %rdx
movdqu -0x10(%rsi), %xmm1
movdqu -0x20(%rsi), %xmm2
movdqu -0x30(%rsi), %xmm3
movdqu -0x40(%rsi), %xmm4
movdqu -0x50(%rsi), %xmm5
movdqu -0x60(%rsi), %xmm6
movdqu -0x70(%rsi), %xmm7
movdqu -0x80(%rsi), %xmm8
movdqa %xmm1, -0x10(%rdi)
movdqa %xmm2, -0x20(%rdi)
movdqa %xmm3, -0x30(%rdi)
movdqa %xmm4, -0x40(%rdi)
movdqa %xmm5, -0x50(%rdi)
movdqa %xmm6, -0x60(%rdi)
movdqa %xmm7, -0x70(%rdi)
movdqa %xmm8, -0x80(%rdi)
lea -0x80(%rsi), %rsi
lea -0x80(%rdi), %rdi
jae L(gobble_ll_loop)
为什么memmove比memcpy更快? 我希望memcpy能够复制内存页面,这应该比循环更快。 在最糟糕的情况下,我希望memcpy能够像移动一样快。
PS:我知道我不能用我的代码中的memcpy替换memmove。 我知道代码示例混合了C和C ++。 这个问题实际上只是为了学术目的。
更新1
基于各种答案,我运行了一些测试的变体。
memset(b2, 0, BUFFERSIZE...)
)时,第一次执行memcpy的速度也会更快。 结果如下:
memcpy 0.0118526
memcpy 0.0119105
memmove (002) 0.0108151
memmove (004) 0.0107122
memmove (008) 0.0107262
memmove (016) 0.0108555
memmove (032) 0.0107171
memmove (064) 0.0106437
memmove (128) 0.0106648
我的结论是:根据@Oliver Charlesworth的评论,操作系统必须在第一次访问memcpy目标缓冲区时立即提交物理内存(如果有人知道如何“证明”这一点,请添加一个答案! )。 另外,正如@Mats Petersson所说,memmove比memcpy更友好。
感谢所有伟大的答案和评论!
您的memmove
调用会将内存一起移动2到128个字节,而您的memcpy
源和目标完全不同。 不知何故,这就是性能差异的原因:如果你复制到同一个地方,你会看到memcpy
结果可能更快,例如在ideone.com上:
memmove (002) 0.0610362
memmove (004) 0.0554264
memmove (008) 0.0575859
memmove (016) 0.057326
memmove (032) 0.0583542
memmove (064) 0.0561934
memmove (128) 0.0549391
memcpy 0.0537919
几乎没有任何内容 - 没有证据表明写回内存页中已经存在的故障会产生很大影响,我们当然不会看到时间减半......但它确实表明,在比较时, memcpy
不必要地变慢苹果换苹果。
当你使用memcpy
,写入需要进入缓存。 当你使用memmove
,当你向前复制一小步时,你正在复制的内存将已经在缓存中(因为它被读取了2,4,16或128个字节“后退”)。 尝试做一个memmove
目的地是几兆字节(> 4 *高速缓存大小),我怀疑(但不能打扰测试),你会得到类似的结果。
我保证当你做大内存操作时,ALL都是关于缓存维护的。
历史上,memmove和memcopy是相同的功能。 他们以相同的方式工作,并具有相同的实施。 然后意识到memcopy不需要被定义(并且经常不被定义)来以任何特定方式处理重叠区域。
最终结果是,memmove被定义为以特定方式处理重叠区域,即使这会影响性能。 Memcopy应该使用可用于非重叠区域的最佳算法。 实现通常几乎完全相同。
你遇到的问题是x86硬件有太多变化,所以不可能知道哪种移动内存的方法是最快的。 即使你认为在一种情况下你有一个结果,就像在内存布局中有一个不同的'步幅'一样可能导致高速缓存性能大不相同。
你可以基准你实际做的或忽略问题,并依靠C库的基准测试。
编辑:哦,还有最后一件事; 转移大量内存内容非常缓慢。 我猜你的应用程序运行得更快,像一个简单的B-Tree实现来处理你的整数。 (哦,你好,)
编辑2:总结我在评论中的扩展:微基准是这里的问题,它不是衡量你的想法。 给memcpy和memmove的任务有很大不同。 如果使用memmove或memcpy将给Memcpy的任务重复多次,则最终结果将不取决于您使用的存储器移位功能,除非区域重叠。
链接地址: http://www.djcxy.com/p/89585.html