当 memcpy() 比 memmove() 更快时,什么是真正重要的情况?
题
关键区别 之间 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);
由于能够承担两个指针 s1
和 s2
不要指出重叠的内存,直接的C实现 memcpy
能够利用它来生成更有效的代码而不诉诸汇编器,请参阅 这里 更多。我确定 memmove
可以做到这一点,但是上面我看到的那些需要进行其他检查 eglibc
, ,这意味着性能成本可能比用于这些功能的C实现的单个分支略多。
好, memmove
当源和目标重叠时,必须向后复制, 和 来源在目的地之前。因此,某些实现 memmove
只需在源位于目的地之前的源时向后复制,而无需考虑两个区域是否重叠。
质量实施 memmove
可以检测区域是否重叠,并在没有重叠时进行前拷贝。在这种情况下,与 memcpy
仅仅是重叠检查。
简单地, memmove
需要测试重叠,然后做适当的事情;和 memcpy
, ,有人断言没有重叠,因此不需要其他测试。
话虽如此,我看到了具有完全相同的代码的平台 memcpy
和 memmove
.
当然有可能 memcpy
只是打电话 memmove
, ,在这种情况下,使用没有好处 memcpy
. 。在另一个极端上,实施者可能会假设 memmove
很少使用,并以C中最简单的字节循环实现,在这种情况下,它可能比优化的速度要慢十倍 memcpy
. 。正如其他人所说的,最有可能的情况是 memmove
用途 memcpy
当它检测到可能的远期副本时,但是某些实现可以简单地比较源地址和目标地址而不寻找重叠。
话虽如此,我建议不要使用 memmove
除非您将数据转移到单个缓冲区中。它可能不会慢,但是再说一次,可能是,为什么当您知道不需要时会冒险 memmove
?
只是简化并始终使用 memmove
. 。始终正确的函数要比只有一半时间的函数要好。
在大多数实施中,在确定两者的行为的任何情况下,MemMove()函数调用的成本都不会大大大于memcpy()。但是,尚未提及两点:
- 在某些实现中,确定地址重叠可能是昂贵的。标准C中没有办法确定源和目的地对象是否指向相同的记忆区域,因此,在没有自发引起猫和狗的情况下,无法使用大于或少的操作员。彼此相处(或调用其他未定义的行为)。任何实际实施可能都会有一些有效的方法来确定指针是否重叠,但是标准不需要这种手段存在。完全写在便携式C上的memmove()函数在许多平台上的执行时间至少是纪念()也完全写在便携式C中的两倍。
- 在执行此操作时,允许实现在串联扩展功能不会改变其语义。在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安全的。不过,我不知道有任何这样的优化。