关键区别 之间 memcpy()memmove() 就是它 memmove() 当源和目标重叠时将正常工作。当缓冲区肯定不重叠时 memcpy() 更可取 因为它是 潜在地 快点。

让我烦恼的是这个 潜在地. 。这是微观优化还是有真正重要的例子 memcpy() 速度更快,所以我们确实需要使用 memcpy() 并且不坚持 memmove() 到处?

有帮助吗?

解决方案

最好的情况是打电话 memcpy 而不是 memmove 将保存指针比较和条件分支。对于大型副本来说,这完全是微不足道的。如果您要制作许多小副本,那么可能值得衡量差异;这是你判断它是否重要的​​唯一方法。

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

其他提示

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

阅读 eglibc-2.11.1 两者的代码 memcpy()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);

由于能够承担两个指针 s1s2 不要指出重叠的内存,直接的C实现 memcpy 能够利用它来生成更有效的代码而不诉诸汇编器,请参阅 这里 更多。我确定 memmove 可以做到这一点,但是上面我看到的那些需要进行其他检查 eglibc, ,这意味着性能成本可能比用于这些功能的C实现的单个分支略多。

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

质量实施 memmove 可以检测区域是否重叠,并在没有重叠时进行前拷贝。在这种情况下,与 memcpy 仅仅是重叠检查。

简单地, memmove 需要测试重叠,然后做适当的事情;和 memcpy, ,有人断言没有重叠,因此不需要其他测试。

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

当然有可能 memcpy 只是打电话 memmove, ,在这种情况下,使用没有好处 memcpy. 。在另一个极端上,实施者可能会假设 memmove 很少使用,并以C中最简单的字节循环实现,在这种情况下,它可能比优化的速度要慢十倍 memcpy. 。正如其他人所说的,最有可能的情况是 memmove 用途 memcpy 当它检测到可能的远期副本时,但是某些实现可以简单地比较源地址和目标地址而不寻找重叠。

话虽如此,我建议不要使用 memmove 除非您将数据转移到单个缓冲区中。它可能不会慢,但是再说一次,可能是,为什么当您知道不需要时会冒险 memmove?

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

在大多数实施中,在确定两者的行为的任何情况下,MemMove()函数调用的成本都不会大大大于memcpy()。但是,尚未提及两点:

  1. 在某些实现中,确定地址重叠可能是昂贵的。标准C中没有办法确定源和目的地对象是否指向相同的记忆区域,因此,在没有自发引起猫和狗的情况下,无法使用大于或少的操作员。彼此相处(或调用其他未定义的行为)。任何实际实施可能都会有一些有效的方法来确定指针是否重叠,但是标准不需要这种手段存在。完全写在便携式C上的memmove()函数在许多平台上的执行时间至少是纪念()也完全写在便携式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()执行此类优化。我不知道有什么能与memmove一起做的,尽管在某些情况下,Memcpy的优化版本可能会提供与Memmmove相同的语义。例如,如果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

即使地址范围重叠,这也可以正常工作,因为它有效地制作了整个区域的副本(在寄存器中),然后在编写任何一个区域之前要移动。从理论上讲,编译器可以通过查看将其踩入memcpy()是否会产生安全的实现来处理memmove(),即使地址范围重叠也是安全的,并在替换memcpy()实现的情况下致电_memmove安全的。不过,我不知道有任何这样的优化。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top