我正在调查一个应用程序中的性能热点,该应用程序将 50% 的时间用于 memmove(3)。该应用程序将数百万个 4 字节整数插入到排序数组中,并使用 memmove 将数据“向右”移动,以便为插入的值腾出空间。
我的期望是复制内存非常快,我很惊讶在 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 is 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 缓存更友好。
感谢所有精彩的答案和评论!
原文由 cruppstahl 发布,翻译遵循 CC BY-SA 4.0 许可协议
您的
memmove
调用将内存改组 2 到 128 个字节,而您的memcpy
源和目标完全不同。不知何故,这是性能差异的原因:如果你复制到同一个地方,你会看到memcpy
最终可能会快一点,例如在 ideone.com 上:虽然其中几乎没有任何内容 - 没有证据表明写回内存中已经出现错误的页面会产生 很大 影响,而且我们当然没有看到时间减半……但它确实表明制作
memcpy
没有任何问题---
与苹果换苹果相比,速度不必要地慢。