簡體   English   中英

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

[英]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()

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實現能夠利用它來生成更高效的代碼,而無需借助匯編程序,請參閱此處了解更多信息。 我確信memmove可以做到這一點,但是我在eglibc看到的那些上面需要額外的檢查,這意味着對於這些函數的C實現,性能成本可能略高於單個分支。

充其量,調用memcpy而不是memmove將保存指針比較和條件分支。 對於大型副本,這是完全無關緊要的。 如果您正在做許多小型副本,那么可能值得衡量差異; 這是唯一可以判斷它是否重要的​​方法。

它絕對是一種微觀優化,但這並不意味着當您可以輕松證明它是安全的時候不應該使用memcpy 過早的悲觀情緒是多惡的根源。

好吧, memmove必須在源和目標重疊時向后復制, 並且源位於目標之前。 因此, memmove某些實現只是在源位於目標之前時向后復制,而不考慮這兩個區域是否重疊。

memmove的高質量實現可以檢測區域是否重疊,並在不執行時進行前向復制。 在這種情況下,與memcpy相比,唯一的額外開銷就是重疊檢查。

簡單地說, memmove需要測試重疊然后做適當的事情; 使用memcpy ,一個斷言沒有重疊,因此不需要額外的測試。

話雖如此,我已經看到了具有完全相同的memcpymemmove代碼的平台。

memcpy當然可能僅僅是對memmove的調用,在這種情況下使用memcpy沒有任何好處。 另一方面,實現者可能很少使用memmove ,並且在C中使用最簡單的一次一個字節循環來實現它,在這種情況下,它可能比優化的memcpy慢十倍。 正如其他人所說,最有可能的情況是memmove在檢測到正向拷貝可能時使用memcpy ,但是某些實現可能只是比較源地址和目標地址而不尋找重疊。

話雖如此,我建議永遠不要使用memmove除非你在一個緩沖區內移動數據。 它可能不會慢,但話又說回來,那么為什么當你知道不需要memmove時冒險呢?

只需簡化並始終使用memmove 一直都是正確的功能比只有一半時間的功能更好。

完全有可能在大多數實現中,memmove()函數調用的成本在定義兩者行為的任何場景中都不會比memcpy()大得多。 但是,有兩點尚未提及:

  1. 在一些實現中,地址重疊的確定可能是昂貴的。 在標准C中無法確定源和目標對象是否指向相同的內存分配區域,因此無法使用大於或小於運算符而不會自發地導致貓和狗彼此相處(或調用其他未定義的行為)。 任何實際實現都可能具有一些確定指針是否重疊的有效方法,但是標准不要求存在這樣的方法。 完全用可移植C編寫的memmove()函數在許多平台上執行可能需要至少兩倍的時間來執行,而memcpy()也完全用便攜式C編寫。
  2. 允許實現在線擴展函數,這樣做不會改變它們的語義。 在80x86編譯器上,如果ESI和EDI寄存器沒有發生任何重要的事情,memcpy(src,dest,1234)可以生成代碼:
    \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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM