当memcpy()比memmove()更快时,真正的重要案例是什么?

时间:2010-09-13 13:48:22

标签: c++ c memory memcpy memmove

memcpy()memmove()之间的{p> The key differencememmove()在源和目标重叠时可以正常工作。缓冲区肯定不会重叠memcpy() is preferable,因为它可能更快。

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

7 个答案:

答案 0 :(得分:19)

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

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

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

Update0

除了上面列出的假设和观察之外,实际上还有一个区别。从C99开始,这两个函数存在以下原型:

void *memcpy(void * restrict s1, const void * restrict s2, size_t n);
void *memmove(void * s1, const void * s2, size_t n);

由于能够假设2个指针s1s2没有指向重叠内存,memcpy的简单C实现能够利用它来生成更高效的代码而无需求助于汇编程序,请参阅here了解更多信息。我确信memmove可以做到这一点,但是在我eglibc中看到的那些上面需要进行额外的检查,这意味着对于这些函数的C实现,性能成本可能略高于单个分支。

答案 1 :(得分:13)

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

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

答案 2 :(得分:4)

好吧,memmove必须在源和目标重叠时向后复制,源位于目标之前。因此,memmove的某些实现只是在源位于目标之前时向后复制,而不考虑这两个区域是否重叠。

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

答案 3 :(得分:2)

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

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

答案 4 :(得分:2)

memcpy肯定只是对memmove的调用,在这种情况下使用memcpy没有任何好处。另一方面,假设memmove假设memcpy很少被使用,并且在C中使用最简单的一次一个字节循环来实现它,在这种情况下它可能比一个慢十倍。优化memmove。正如其他人所说,最有可能的情况是memcpy在检测到正向拷贝是可能的时使用memmove,但有些实现可能只是比较源地址和目标地址而不寻找重叠。

话虽如此,我建议不要使用memmove,除非你在一个缓冲区内移动数据。它可能不会慢,但是可能会更快,所以当你知道不需要{{1}}时,为什么要冒风险?

答案 5 :(得分:2)

简化并始终使用memmove。一直都是正确的功能比只有一半时间的功能更好。

答案 6 :(得分:2)

在大多数实现中,完全有可能在任何定义了两者行为的场景中,memmove()函数调用的成本都不会比memcpy()大得多。但是还有两点尚未提及:

  1. 在一些实现中,地址重叠的确定可能是昂贵的。在标准C中无法确定源和目标对象是否指向相同的内存分配区域,因此无法使用大于或小于运算符而不会自发地导致猫和狗彼此相处(或调用其他未定义的行为)。任何实际实现都可能具有一些确定指针是否重叠的有效方法,但是标准不要求存在这样的方法。完全用可移植C编写的memmove()函数在许多平台上执行可能需要至少两倍的时间来执行,而memcpy()也完全用便携式C编写。
  2. 允许实现在线扩展函数,这样做不会改变它们的语义。在80x86编译器上,如果ESI和EDI寄存器没有发生任何重要的事情,memcpy(src,dest,1234)可以生成代码:
      mov esi,[src]
      mov edi,[dest]
      mov ecx,1234/4 ; Compiler could notice it's a constant
      cld
      rep movsl
    
    这将采用相同数量的内联代码,但运行速度比:
      push [src]
      push [dest]
      push dword 1234
      call _memcpy
    
      ...
    
    _memcpy:
      push ebp
      mov  ebp,esp
      mov  ecx,[ebp+numbytes]
      test ecx,3   ; See if it's a multiple of four
      jz   multiple_of_four
    
    multiple_of_four:
      push esi ; Can't know if caller needs this value preserved
      push edi ; Can't know if caller needs this value preserved
      mov esi,[ebp+src]
      mov edi,[ebp+dest]
      rep movsl
      pop edi
      pop esi
      ret  
    

相当多的编译器将使用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安全。不过,我不知道有什么可以做这样的优化。