为什么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

基于各种答案,我运行了一些测试的变体。

  • 当运行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更友好。

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


    您的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

    上一篇: Why is memmove faster than memcpy?

    下一篇: Bitwise shift of buffer in CUDA