首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >为什么memmove比memcpy快?

为什么memmove比memcpy快?
EN

Stack Overflow用户
提问于 2015-02-20 15:45:08
回答 4查看 31.6K关注 0票数 95

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

我的期望是复制内存是非常快的,我很惊讶在memmove上花了这么多时间。但是后来我有了一个想法,memmove之所以很慢,是因为它移动重叠的区域,这必须在一个紧密的循环中实现,而不是复制大量的内存页面。我写了一个小的微基准测试,看看memcpy和memmove之间是否存在性能差异,希望memcpy能轻松取胜。

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

这是我的基准测试。它使用memcpy复制100mb,然后使用memmove移动大约100mb;源和目标重叠。尝试了源和目标的各种“距离”。每个测试运行10次,打印平均时间。

https://gist.github.com/cruppstahl/78a57cdf937bca3d062c

这是在核心i5上的结果(Linux3.5.0-54-generic#81~Predicise1-Ubuntu SMP x86_64 GNU/Linux,gcc是4.6.3 (Ubuntu/Linaro4.6.3-1ubuntu5)。括号中的数字是源和目标之间的距离(间隙大小):

代码语言:javascript
复制
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行以上)

代码语言:javascript
复制
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的目标缓冲区( faster.

  • memcpy )时,第一次运行的memcpy也是
  1. 仍然比memmove慢一点。

结果如下:

代码语言:javascript
复制
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缓存更友好。

感谢所有精彩的回答和评论!

EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2015-02-20 15:56:07

当您的memcpy源和目标完全不同时,您的memmove调用会将内存混洗2到128个字节。不知何故,这就是性能差异的原因:如果你复制到相同的位置,你会看到memcpy最终可能会稍微快一点,例如在ideone.com

代码语言:javascript
复制
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变得不必要地变慢并没有错。

票数 59
EN

Stack Overflow用户

发布于 2015-02-20 16:53:43

当您使用memcpy时,写操作需要进入缓存。当您使用memmove时,当您向前复制一小步时,您正在复制的内存将已经在缓存中(因为它被读取了2、4、16或128个字节的“后退”)。尝试执行目标为几兆字节(> 4*缓存大小)的memmove,我怀疑(但不必费心测试)您会得到类似的结果。

我保证,当您执行大内存操作时,所有操作都与缓存维护有关。

票数 27
EN

Stack Overflow用户

发布于 2015-02-20 16:06:33

从历史上看,memmove和memcopy是相同的功能。它们以相同的方式工作,具有相同的实现。然后人们意识到,memcopy不需要(通常也不是)定义来以任何特定的方式处理重叠区域。

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

您遇到的问题是,x86硬件有如此之多的变体,以至于无法判断哪种转移内存的方法将是最快的。即使你认为在一种情况下你有一个结果,像在内存布局中有一个不同的“步幅”这样简单的事情也会导致巨大不同的缓存性能。

您可以对实际正在做的事情进行基准测试,也可以忽略该问题并依赖于为C库所做的基准测试。

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

Edit2:在评论中总结我的扩展:微基准是这里的问题,它不是衡量你认为它是什么。分配给memcpy和memmove的任务彼此有很大的不同。如果使用memmove或memcpy多次重复指定给memcpy的任务,则最终结果将不取决于您使用哪个内存移位函数,除非区域重叠。

票数 16
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/28623895

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档