为什么 memmove 比 memcpy 快?
Why is memmove faster than memcpy?
我正在调查花费 50% 的应用程序中的性能热点
它在 memmove(3) 中的时间。应用程序插入数百万个 4 字节整数
放入已排序的数组中,并使用 memmove 将数据 "to the right" 移动到
为了使 space 为插入的值。
我的期望是复制内存非常快,我很惊讶
这么多时间都花在了 memmove 上。但后来我想到了 memmove
很慢,因为它正在移动重叠区域,必须实现
在一个紧密的循环中,而不是复制大内存页。我写了一个小
微基准测试以找出两者之间是否存在性能差异
memcpy 和 memmove,期待 memcpy 获胜。
我 运行 我在两台机器(核心 i5、核心 i7)上的基准测试,看到 memmove 是
实际上比 memcpy 更快,在较旧的 core i7 上甚至快了将近两倍!
现在我正在寻找解释。
这是我的基准。它使用 memcpy 复制 100 MB,然后使用 memmove 移动大约 100 MB;源和目标重叠。各种 "distances"
尝试了源和目标。每次测试运行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 行)
L(gobble_ll_loop):
prefetchnta -0x1c0(%rsi)
prefetchnta -0x280(%rsi)
prefetchnta -0x1c0(%rdi)
prefetchnta -0x280(%rdi)
sub [=11=]x80, %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两次,那么第二次运行比第一次快
- 当"touching" 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 目标缓冲区后立即提交物理内存(如果有人知道如何 "proof"然后请添加答案!)。此外,正如@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
不必要地变慢并没有错。
从历史上看,memmove 和 memcpy 是同一个函数。它们以相同的方式工作并且具有相同的实现。然后意识到不需要(而且通常不需要)定义 memcpy 来以任何特定方式处理重叠区域。
最终结果是 memmove 被定义为以特定方式处理重叠区域,即使这会影响性能。 memcpy 应该使用可用于非重叠区域的最佳算法。这些实现通常几乎相同。
您 运行 遇到的问题是 x86 硬件的变体如此之多,以至于无法判断哪种移动内存的方法最快。即使你认为你在一种情况下得到了结果,像内存布局中不同的 'stride' 这样简单的事情也会导致缓存性能大不相同。
您可以对您实际执行的操作进行基准测试,也可以忽略该问题并依赖为 C 库完成的基准测试。
编辑:哦,还有最后一件事;移动大量内存内容非常慢。我猜你的应用程序会 运行 更快地使用类似简单的 B-Tree 实现来处理你的整数。 (哦,你是,好吧)
Edit2:在评论中总结我的扩展:
微基准是这里的问题,它并没有衡量您的想法。分配给 memcpy 和 memmove 的任务彼此有很大不同。如果分配给 memcpy 的任务用 memmove 或 memcpy 重复几次,最终结果将不取决于您使用的内存移位函数,除非区域重叠。
当您使用 memcpy
时,写入需要进入缓存。当你使用 memmove
时,当你向前复制一小步时,你复制过来的内存将已经在缓存中(因为它被读取了 2、4、16 或 128 个字节 "back")。尝试做一个 memmove
,其中目标是几兆字节(> 4 * 缓存大小),我怀疑(但懒得去测试)你会得到类似的结果。
大内存操作我保证ALL都是关于缓存维护的
"memcpy is more efficient than memmove." 在您的情况下,当您 运行 这两个功能时,您很可能没有做完全相同的事情。
一般来说,只有在必要时才使用 memmove。当源区域和目标区域很可能重叠时使用它。
参考文献:https://www.youtube.com/watch?v=Yr1YnOVG-4g Jerry Cain 博士,(斯坦福介绍系统讲座 - 7)时间:36:00
我正在调查花费 50% 的应用程序中的性能热点 它在 memmove(3) 中的时间。应用程序插入数百万个 4 字节整数 放入已排序的数组中,并使用 memmove 将数据 "to the right" 移动到 为了使 space 为插入的值。
我的期望是复制内存非常快,我很惊讶 这么多时间都花在了 memmove 上。但后来我想到了 memmove 很慢,因为它正在移动重叠区域,必须实现 在一个紧密的循环中,而不是复制大内存页。我写了一个小 微基准测试以找出两者之间是否存在性能差异 memcpy 和 memmove,期待 memcpy 获胜。
我 运行 我在两台机器(核心 i5、核心 i7)上的基准测试,看到 memmove 是 实际上比 memcpy 更快,在较旧的 core i7 上甚至快了将近两倍! 现在我正在寻找解释。
这是我的基准。它使用 memcpy 复制 100 MB,然后使用 memmove 移动大约 100 MB;源和目标重叠。各种 "distances" 尝试了源和目标。每次测试运行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 行)
L(gobble_ll_loop):
prefetchnta -0x1c0(%rsi)
prefetchnta -0x280(%rsi)
prefetchnta -0x1c0(%rdi)
prefetchnta -0x280(%rdi)
sub [=11=]x80, %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两次,那么第二次运行比第一次快
- 当"touching" 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 目标缓冲区后立即提交物理内存(如果有人知道如何 "proof"然后请添加答案!)。此外,正如@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
不必要地变慢并没有错。
从历史上看,memmove 和 memcpy 是同一个函数。它们以相同的方式工作并且具有相同的实现。然后意识到不需要(而且通常不需要)定义 memcpy 来以任何特定方式处理重叠区域。
最终结果是 memmove 被定义为以特定方式处理重叠区域,即使这会影响性能。 memcpy 应该使用可用于非重叠区域的最佳算法。这些实现通常几乎相同。
您 运行 遇到的问题是 x86 硬件的变体如此之多,以至于无法判断哪种移动内存的方法最快。即使你认为你在一种情况下得到了结果,像内存布局中不同的 'stride' 这样简单的事情也会导致缓存性能大不相同。
您可以对您实际执行的操作进行基准测试,也可以忽略该问题并依赖为 C 库完成的基准测试。
编辑:哦,还有最后一件事;移动大量内存内容非常慢。我猜你的应用程序会 运行 更快地使用类似简单的 B-Tree 实现来处理你的整数。 (哦,你是,好吧)
Edit2:在评论中总结我的扩展: 微基准是这里的问题,它并没有衡量您的想法。分配给 memcpy 和 memmove 的任务彼此有很大不同。如果分配给 memcpy 的任务用 memmove 或 memcpy 重复几次,最终结果将不取决于您使用的内存移位函数,除非区域重叠。
当您使用 memcpy
时,写入需要进入缓存。当你使用 memmove
时,当你向前复制一小步时,你复制过来的内存将已经在缓存中(因为它被读取了 2、4、16 或 128 个字节 "back")。尝试做一个 memmove
,其中目标是几兆字节(> 4 * 缓存大小),我怀疑(但懒得去测试)你会得到类似的结果。
大内存操作我保证ALL都是关于缓存维护的
"memcpy is more efficient than memmove." 在您的情况下,当您 运行 这两个功能时,您很可能没有做完全相同的事情。
一般来说,只有在必要时才使用 memmove。当源区域和目标区域很可能重叠时使用它。
参考文献:https://www.youtube.com/watch?v=Yr1YnOVG-4g Jerry Cain 博士,(斯坦福介绍系统讲座 - 7)时间:36:00