我正在研究某个应用程序中的性能热点,该应用程序将其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-通用#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与memmove一样快。
PS:我知道我无法在代码中用memcpy代替memmove。我知道代码示例混合了C和C ++。这个问题实际上只是出于学术目的。
更新1
我根据各种答案对测试进行了一些变化。
- 当运行memcpy两次时,第二次运行比第一次运行快。
- 当“触摸” memcpy(
memset(b2, 0, BUFFERSIZE...)
)的目标缓冲区时,memcpy 的第一次运行也更快。 - memcpy仍然比memmove慢一点。
结果如下:
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更友好。
感谢您提供的所有出色答案和评论!