为什么memmove比memcpy更快?

我正在调查一个应用程序中的性能热点,这个应用程序在memmove(3)中花费了50%的时间。 应用程序将数百万个4字节的整数插入到已sorting的数组中,并使用memmove将数据“向右移动”,以便为插入值腾出空间。

我的期望是复制记忆速度非常快,我惊讶于花费了太多的时间。 但是后来我有一个想法,就是memmove很慢,因为它正在移动重叠的区域,必须在严格的循环中执行,而不是复制大量的内存。 我写了一个小的微型基准来看看memcpy和memmove之间是否存在性能差异,希望memcpy赢得双手。

我在两台机器(核心i5,核心i7)上运行我的基准testing,看到memmove实际上比memcpy更快,在旧的核心i7上甚至快了近一倍! 现在我正在寻找解释。

这是我的基准。 它用memcpy复制100 MB,然后用memmove移动大约100 MB。 来源和目的地重叠。 尝试了源和目的地的各种“距离”。 每个testing运行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优化的汇编代码实现的,从后向前复制。 它使用硬件预取将数据加载到caching中,并将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:我知道我不能用我的代码中的memcpyreplacememmove。 我知道代码示例混合了C和C ++。 这个问题实际上只是为了学术目的。

更新1

根据各种答案,我运行了一些testing的变体。

  1. 当运行memcpy两次时,第二次运行比第一次快。
  2. 当“触摸”memcpy的目标缓冲区( memset(b2, 0, BUFFERSIZE...) )时,第一次执行memcpy的速度也会更快。
  3. 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目标缓冲区时必须提交物理内存(如果有人知道如何“certificate”这一点,请添加一个答案! )。 另外,正如@Mats Petersson所说,memmove比memcpy更加友善。

感谢所有伟大的答案和评论!

你的memmove调用将2到128个字节的内存混合memmove ,而你的memcpy源和目标是完全不同的。 不知何故,这是性能差异的原因:如果你复制到同一个地方,你会看到memcpy结束可能更快smidge ,例如在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 ,写入需要进入caching。 当你使用memmove ,当你向前复制一小步时,你正在复制的内存将已经在caching中(因为它被读取了2,4,16或128个字节“后退”)。 尝试做一个memmove目的地是几兆字节(> 4 *高速caching大小),我怀疑(但不能打扰testing),你会得到类似的结果。

我保证当做大内存操作时,ALL都是关于caching维护的。

历史上,memmove和memcopy是相同的function。 他们以相同的方式工作,并有相同的实施。 然后意识到memcopy不需要被定义(并且经常不被定义)来以任何特定方式处理重叠区域。

最终结果是,memmove被定义为以特定方式处理重叠区域,即使这会影响性能。 Memcopy应该使用可用于非重叠区域的最佳algorithm。 这些实现通常几乎完全相同。

你遇到的问题是x86硬件有太多的变化,所以不可能知道哪种内存转移方法是最快的。 即使你认为你在某种情况下有一个结果,就像在内存布局中有一个不同的“步幅”一样,会导致高速caching的性能大不相同。

你既可以基准你正在做什么,也可以忽略这个问题,并依赖于C库的基准testing。

编辑:哦,还有最后一件事; 大量移动内存内容非常缓慢。 我猜你的应用程序运行得更快,像一个简单的B-Tree实现来处理你的整数。 (哦,你好,)

编辑2:在评论中总结我的扩展:微基准是这里的问题,它不是衡量你的想法。 给memcpy和memmove的任务有很大不同。 如果使用memmove或memcpy将给memcpy的任务重复多次,则最终结果将不取决于您使用的存储器移位function,除非区域重叠。

“memcpy比memmove更有效率”。 就你而言,当你运行这两个函数的时候,你可能不会做同样的事情。

一般来说,只有在必须的时候才使用memmove。 当来源和目的地区域重叠的情况非常合理时,再使用它。

参考: https : //www.youtube.com/watch?v= Yr1YnOVG-4g杰里·凯恩博士(斯坦福大学介绍系统讲座 – 7)时间:36:00