memcpy()
和之间的主要区别memmove()
在于,memmove()
当源和目标重叠时可以正常工作。当缓冲区肯定不重叠时,memcpy() 是可取的,因为它可能更快。
困扰我的是这潜在的问题。它是一个微优化还是有真正重要的例子,什么时候memcpy()
更快,以便我们真的需要使用而不是到处都memcpy()
坚持?memmove()
memmove()
如果编译器无法推断出不可能重叠,则至少有一个隐式分支可以向前或向后复制。这意味着,如果没有优化 , 的能力memcpy()
,memmove()
至少会慢一个分支,并且内联指令占用的任何额外空间来处理每种情况(如果内联是可能的)。
阅读eglibc-2.11.1
两者的代码memcpy()
并memmove()
确认这是可疑的。此外,在向后复制期间不可能进行页面复制,只有在没有重叠机会的情况下才能显着加快速度。
总之,这意味着:如果您可以保证区域不重叠,则选择memcpy()
过度memmove()
可以避免分支。如果源和目标包含相应的页面对齐和页面大小的区域,并且不重叠,则某些体系结构可以为这些区域使用硬件加速副本,无论您是否调用memmove()
或memcpy()
.
实际上,除了我上面列出的假设和观察之外,还有一个不同之处。从 C99 开始,这 2 个函数存在以下原型:
void *memcpy(void * restrict s1, const void * restrict s2, size_t n);
void *memmove(void * s1, const void * s2, size_t n);
由于能够假设 2 个指针s1
并且s2
不指向重叠的内存,直接的 C 实现memcpy
能够利用它来生成更高效的代码,而无需求助于汇编程序,请参阅此处了解更多信息。我确信memmove
可以做到这一点,但是除了我在 中看到的那些之外,还需要额外的检查eglibc
,这意味着性能成本可能略高于这些函数的 C 实现的单个分支。
充其量,调用memcpy
而不是memmove
将保存指针比较和条件分支。对于大型副本,这完全是微不足道的。如果您正在制作许多小副本,那么可能值得衡量差异;这是您判断它是否重要的唯一方法。
这绝对是一种微优化,但这并不意味着memcpy
当您可以轻松证明它是安全的时您不应该使用它。过早的悲观是万恶之源。
好吧,memmove
当源和目标重叠时必须向后复制,并且源在目标之前。因此,当源在目标之前时,一些memmove
简单地向后复制的实现,而不考虑两个区域是否重叠。
的高质量实现memmove
可以检测区域是否重叠,并在没有重叠时进行前向复制。在这种情况下,唯一的额外开销memcpy
只是重叠检查。
这当然可能memcpy
只是对 的调用memmove
,在这种情况下,使用memcpy
. 在另一个极端情况下,可能很少使用假设的实现memmove
者,并使用 C 中最简单的一次字节循环来实现它,在这种情况下,它可能比优化的memcpy
. 正如其他人所说,最可能的情况是在检测到可以进行正向复制时memmove
使用memcpy
,但某些实现可能只是比较源地址和目标地址而不寻找重叠。
memmove
话虽如此,除非您在单个缓冲区中移动数据,否则我建议不要使用。它可能不会更慢,但话又说回来,它可能是,所以当你知道没有必要时为什么要冒险呢memmove
?
完全有可能在大多数实现中,在定义两者行为的任何场景中,memmove() 函数调用的成本都不会显着高于 memcpy()。不过,有两点尚未提及:
mov esi,[src] mov edi,[目标] 移动 ecx,1234/4 ; 编译器可以注意到它是一个常数 分类 代表移动这将采用相同数量的内联代码,但运行速度比:
推 [src] 推[目的地] 推 dword 1234 调用 _memcpy ... _memcpy: 推送ebp mov ebp,esp mov ecx,[ebp+numbytes] 测试 ecx,3 ; 看看是不是四的倍数 jz multiple_of_four 多个四个: 推esi;不知道调用者是否需要保留此值 推送编辑;不知道调用者是否需要保留此值 mov esi,[ebp+src] mov edi,[ebp+dest] 代表移动 流行音乐 流行音乐 ret
相当多的编译器将使用 memcpy() 执行此类优化。我不知道有什么可以用 memmove 做到这一点,尽管在某些情况下,memcpy 的优化版本可能会提供与 memmove 相同的语义。例如,如果 numbytes 为 20:
; 假设不需要 eax、ebx、ecx、edx、esi 和 edi 中的值 mov esi,[src] mov eax,[esi] mov ebx,[esi+4] mov ecx,[esi+8] mov edx,[esi+12] mov edi,[esi+16] mov esi,[目标] mov [esi],eax mov [esi+4],ebx mov [esi+8],ecx mov [esi+12],edx mov [esi+16],edi
即使地址范围重叠,这也能正常工作,因为它有效地复制了整个区域的副本(在寄存器中),然后再写入任何区域。理论上,编译器可以通过查看是否将 memmove() 视为 memcpy() 来处理 memmove() 是否会产生一个即使地址范围重叠也是安全的实现,并在不会替换 memcpy() 实现的情况下调用 _memmove安全的。不过,我不知道有什么做这种优化的。
简单地说,memmove
需要测试重叠,然后做适当的事情;,memcpy
一个断言没有重叠,因此不需要额外的测试。
话虽如此,我已经看到具有完全相同代码的平台memcpy
和memmove
.
只需简化并始终使用memmove
. 一个始终正确的函数比一个只有一半时间正确的函数要好。