[英]What are real significant cases when memcpy() is faster than memmove()?
memcpy()
和memmove()
之间的关键区别在于,当源和目标重叠时, memmove()
将正常工作。 当缓冲区肯定不重叠时, memcpy()更可取,因为它可能更快。
困扰我的是这个潜在的 。 它是一个微优化还是当memcpy()
更快时有真正重要的例子,所以我们真的需要使用memcpy()
而不是到处都有memmove()
?
如果编译器无法推断出无法重叠,那么至少有一个隐式分支可以向前或向后复制memmove()
。 这意味着如果不能优化memcpy()
, memmove()
至少会被一个分支放慢,并且内联指令占用的任何额外空间都可以处理每种情况(如果可以内联)。
读取memcpy()
和memmove()
的eglibc-2.11.1
代码可以确认这一点。 此外,在向后复制期间不可能进行页面复制,只有在没有重叠的情况下才能获得显着的加速。
总之,这意味着:如果可以保证区域不重叠,那么在memmove()
选择memcpy()
memmove()
可以避免分支。 如果源和目标包含相应的页面对齐和页面大小的区域,并且不重叠,则某些体系结构可以为这些区域使用硬件加速副本,无论您是否调用了memmove()
或memcpy()
。
除了我上面列出的假设和观察之外,实际上还有一个区别。 从C99开始,这两个函数存在以下原型:
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
没有指向重叠的内存,因此memcpy
直接C实现能够利用它来生成更高效的代码,而无需借助汇编程序,请参阅此处了解更多信息。 我确信memmove
可以做到这一点,但是我在eglibc
看到的那些上面需要额外的检查,这意味着对于这些函数的C实现,性能成本可能略高于单个分支。
充其量,调用memcpy
而不是memmove
将保存指针比较和条件分支。 对于大型副本,这是完全无关紧要的。 如果您正在做许多小型副本,那么可能值得衡量差异; 这是唯一可以判断它是否重要的方法。
它绝对是一种微观优化,但这并不意味着当您可以轻松证明它是安全的时候不应该使用memcpy
。 过早的悲观情绪是多恶的根源。
好吧, memmove
必须在源和目标重叠时向后复制, 并且源位于目标之前。 因此, memmove
某些实现只是在源位于目标之前时向后复制,而不考虑这两个区域是否重叠。
memmove
的高质量实现可以检测区域是否重叠,并在不执行时进行前向复制。 在这种情况下,与memcpy
相比,唯一的额外开销就是重叠检查。
简单地说, memmove
需要测试重叠然后做适当的事情; 使用memcpy
,一个断言没有重叠,因此不需要额外的测试。
话虽如此,我已经看到了具有完全相同的memcpy
和memmove
代码的平台。
memcpy
当然可能仅仅是对memmove
的调用,在这种情况下使用memcpy
没有任何好处。 另一方面,实现者可能很少使用memmove
,并且在C中使用最简单的一次一个字节循环来实现它,在这种情况下,它可能比优化的memcpy
慢十倍。 正如其他人所说,最有可能的情况是memmove
在检测到正向拷贝可能时使用memcpy
,但是某些实现可能只是比较源地址和目标地址而不寻找重叠。
话虽如此,我建议永远不要使用memmove
除非你在一个缓冲区内移动数据。 它可能不会慢,但话又说回来,那么为什么当你知道不需要memmove
时冒险呢?
只需简化并始终使用memmove
。 一直都是正确的功能比只有一半时间的功能更好。
完全有可能在大多数实现中,memmove()函数调用的成本在定义两者行为的任何场景中都不会比memcpy()大得多。 但是,有两点尚未提及:
\n mov esi,[src]\n mov edi,[dest]\n mov ecx,1234/4; 编译器可能会注意到它是一个常数\n CLD\n rep movsl\n这将采用相同数量的内联代码,但运行速度比:
\n 推[src]\n 推[dest]\n 推dword 1234\n 打电话给_memcpy\n\n ...\n\n _memcpy:\n 推ebp\n mov ebp,尤其是\n mov ecx,[ebp + numbytes]\n 测试ecx,3; 看看它是否是四的倍数\n jz multiple_of_four\n\n multiple_of_four:\n 推esi; 无法知道调用者是否需要保留此值\n 推edi; 无法知道调用者是否需要保留此值\n mov esi,[ebp + src]\n mov edi,[ebp + dest]\n rep movsl\n pop edi\n 流行esi\n RET \n
相当多的编译器将使用memcpy()执行此类优化。 虽然在某些情况下memcpy的优化版本可能提供与memmove相同的语义,但我不知道有任何与memmove有关的内容。 例如,如果numbytes为20:
; Assuming values in eax, ebx, ecx, edx, esi, and edi are not needed mov esi,[src] mov eax,[esi] mov ebx,[esi+4] mov ecx,[esi+8] mov edx,[esi+12] mov edi,[esi+16] mov esi,[dest] mov [esi],eax mov [esi+4],ebx mov [esi+8],ecx mov [esi+12],edx mov [esi+16],edi
即使地址范围重叠,这也将正常工作,因为它有效地使整个区域的副本(在寄存器中)在其中任何一个被写入之前被移动。 从理论上讲,编译器可以处理memmove(),看看是否将其作为memcpy()生成即使地址范围重叠也会安全的实现,并且在替换memcpy()实现的情况下调用_memmove安全。 不过,我不知道有任何优化。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.