16

memcpy()和之间的主要区别memmove()在于,memmove()当源和目标重叠时可以正常工作。当缓冲区肯定不重叠时,memcpy() 是可取的,因为它可能更快。

困扰我的是这潜在的问题。它是一个微优化还是有真正重要的例子,什么时候memcpy()更快,以便我们真的需要使用而不是到处都memcpy()坚持?memmove()

4

7 回答 7

20

memmove()如果编译器无法推断出不可能重叠,则至少有一个隐式分支可以向前或向后复制。这意味着,如果没有优化 , 的能力memcpy()memmove()至少会慢一个分支,并且内联指令占用的任何额外空间来处理每种情况(如果内联是可能的)。

阅读eglibc-2.11.1两者的代码memcpy()memmove()确认这是可疑的。此外,在向后复制期间不可能进行页面复制,只有在没有重叠机会的情况下才能显着加快速度。

总之,这意味着:如果您可以保证区域不重叠,则选择memcpy()过度memmove()可以避免分支。如果源和目标包含相应的页面对齐和页面大小的区域,并且不重叠,则某些体系结构可以为这些区域使用硬件加速副本,无论您是否调用memmove()memcpy().

更新0

实际上,除了我上面列出的假设和观察之外,还有一个不同之处。从 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 实现的单个分支。

于 2010-09-13T14:10:56.517 回答
13

充其量,调用memcpy而不是memmove将保存指针比较和条件分支。对于大型副本,这完全是微不足道的。如果您正在制作许多小副本,那么可能值得衡量差异;这是您判断它是否重要的​​唯一方法。

这绝对是一种微优化,但这并不意味着memcpy当您可以轻松证明它是安全的时您不应该使用它。过早的悲观是万恶之源。

于 2010-09-13T14:07:08.443 回答
4

好吧,memmove当源和目标重叠时必须向后复制,并且源在目标之前。因此,当源在目标之前时,一些memmove简单地向后复制的实现,而不考虑两个区域是否重叠。

的高质量实现memmove可以检测区域是否重叠,并在没有重叠时进行前向复制。在这种情况下,唯一的额外开销memcpy只是重叠检查。

于 2010-09-13T13:51:11.657 回答
2

这当然可能memcpy只是对 的调用memmove,在这种情况下,使用memcpy. 在另一个极端情况下,可能很少使用假设的实现memmove者,并使用 C 中最简单的一次字节循环来实现它,在这种情况下,它可能比优化的memcpy. 正如其他人所说,最可能的情况是在检测到可以进行正向复制时memmove使用memcpy,但某些实现可能只是比较源地址和目标地址而不寻找重叠。

memmove话虽如此,除非您在单个缓冲区中移动数据,否则我建议不要使用。它可能不会更慢,但话又说回来,它可能是,所以当你知道没有必要时为什么要冒险呢memmove

于 2010-09-13T18:01:32.313 回答
2

完全有可能在大多数实现中,在定义两者行为的任何场景中,memmove() 函数调用的成本都不会显着高于 memcpy()。不过,有两点尚未提及:

  1. 在一些实现中,地址重叠的确定可能是昂贵的。标准 C 中无法确定源对象和目标对象是否指向同一分配的内存区域,因此无法在它们上使用大于或小于运算符而不会自发地导致猫和狗彼此相处(或调用其他未定义行为)。任何实际的实现都可能有一些有效的方法来确定指针是否重叠,但标准并不要求存在这种方法。完全用可移植 C 编写的 memmove() 函数在许多平台上的执行时间可能至少是完全用可移植 C 编写的 memcpy() 的两倍。
  2. 当这样做不会改变它们的语义时,允许实现内联扩展函数。在 80x86 编译器上,如果 ESI 和 EDI 寄存器碰巧没有保存任何重要的东西,则 memcpy(src, dest, 1234) 可以生成代码:
      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安全的。不过,我不知道有什么做这种优化的。

于 2011-11-20T22:16:30.617 回答
2

简单地说,memmove需要测试重叠,然后做适当的事情;,memcpy一个断言没有重叠,因此不需要额外的测试。

话虽如此,我已经看到具有完全相同代码的平台memcpymemmove.

于 2010-09-13T13:52:57.503 回答
2

只需简化并始终使用memmove. 一个始终正确的函数比一个只有一半时间正确的函数要好。

于 2010-09-13T18:12:08.793 回答