簡體   English   中英

strncpy / memcpy / memmove是逐字節還是以其他方式有效地復制數據?

[英]Do strncpy/memcpy/memmove copy the data byte by byte or in another efficiently way?

眾所周知,在x86 / x86_64這樣的多字節字計算機中,逐字復制/移動大量內存(每步4或8個字節)比逐字節更有效。

我很好奇strncpy / memcpy / memmove會做什么,以及它們如何處理內存字對齊。

char buf_A[8], buf_B[8];

// I often want to code as this
*(double*)buf_A = *(double*)buf_B;

//in stead of this
strcpy(buf_A, buf_B);
// but it worsen the readability of my codes.

通常,您不必過多考慮如何實現memcpy或其他類似功能。 除非你的分析證明你錯了,否則你應該認為它們是有效的。

在實踐中,它確實很好地優化了。 請參閱以下測試代碼:

#include <cstring>

void test(char (&a)[8], char (&b)[8])
{
    std::memcpy(&a,&b,sizeof a);
}

使用g ++ g++ test.cpp -O3 -S -masm=intel命令用g ++ 7.3.0編譯它,我們可以看到以下匯編代碼:

test(char (&) [8], char (&) [8]):

    mov     rax, QWORD PTR [rsi]
    mov     QWORD PTR [rdi], rax
    ret

如您所見,副本不僅內聯,而且還折疊為單個8字節讀寫。

在這種情況下,您可能更喜歡使用memcpy因為這相當於*(double*)buf_A = *(double*)buf_B; 沒有未定義的行為。

你不應該擔心調用memcpy因為默認情況下編譯器假設對memcpy的調用具有c庫中定義的含義。 因此,根據參數的類型和/或編譯時副本大小的知識,編譯器可以選擇不調用c庫函數並內聯更適合的內存復制策略。 在gcc上,您可以使用-fno-builtin編譯器選項禁用此行為: demo

需要編譯器替換memcpy調用,因為memcpy將檢查指針的大小和對齊以使用最有效的內存復制策略(它可能開始像char一樣通過char復制到非常大的塊,使用AVX512指令例)。 這些檢查以及對memcpy的調用成本。

此外,如果您正在尋找效率,您應該關注內存對齊。 所以你可能想要聲明緩沖區的對齊方式:

alignas(8) char buf_A[8];

來自cpp-reference

復制從src指向的對象到dest指向的對象的計數字節。 這兩個對象都被重新解釋為unsigned char數組。

筆記

std :: memcpy意味着內存到內存復制的最快庫例程。 它通常比std :: strcpy更有效,它必須掃描它復制的數據或std :: memmove,它必須采取預防措施來處理重疊輸入。

幾個C ++編譯器將合適的內存復制循環轉換為std :: memcpy調用。

如果嚴格別名禁止檢查與兩種不同類型的值相同的內存,則std :: memcpy可用於轉換值。

所以它應該是復制數據的最快方式。 但請注意,有幾種情況下行為未定義:

如果對象重疊,則行為未定義。

如果dest或src是空指針,則行為是未定義的,即使count為零也是如此。

如果對象可能重疊或不是TriviallyCopyable,則不指定memcpy的行為,並且可能未定義。

strcpy / strncpy是逐字節還是以其他方式有效地復制數據?

C ++和C標准沒有具體說明strcpy / strncpy的實現方式。 他們只描述行為。

有多個標准庫實現,每個都選擇如何實現它們的功能。 可以使用memcpy實現這兩者。 標准也沒有准確描述memcpy的實現,並且多個實現的存在也適用於它。

memcpy可以利用全文復制實現。 的的memcpy如何可以實現短的偽代碼:

if len >= 2 * word size
    copy bytes until destination pointer is aligned to word boundary
    if len >= page size
        copy entire pages using virtual address manipulation
    copy entire words
 copy the trailing bytes that are not aligned to word boundary

要了解特定標准庫實現如何實現strcpy / strncpy / memcpy,您可以閱讀標准庫的源代碼 - 如果您有權訪問它。

更進一步,當在編譯時知道長度時,編譯器甚至可能選擇不使用庫memcpy,而是進行內聯復制。 無論您的編譯器是否具有標准庫函數的內置定義,您都可以在相應編譯器的文檔中找到。

這取決於您使用的編譯器和您正在使用的C運行時庫。 在大多數情況下,string.h函數如memcmpmemcpystrcpumemset等在CPU優化方式下使用匯編實現。

您可以為AMD64架構找到這些函數的GNU libc實現。 如您所見,它可以使用SSE或AVX指令每次迭代復制128位和512位。 Microsoft還將其CRT的源代碼與Visual Studio捆綁在一起(主要是相同的方法,支持MMX,SSE,AVX循環)。

編譯器也對這類函數使用特殊優化,GCC調用它們內置其他編譯器調用它們的內在函數。 即編譯器可以選擇 - 調用庫函數,或生成針對當前上下文的最佳CPU特定匯編代碼。 例如,當memcpy N參數是常量時,即memcpy(dst, src, 128)編譯器可能會生成內聯匯編代碼(類似於mov 16,rcx cls rep stosq ),當它是變量即memcpy(dst,src,bytes) - 編譯器可以插入對庫函數的call _memcpy (類似於call _memcpy

我認為這個頁面上的所有意見和建議都是合理的,但我決定嘗試一些實驗。

令我驚訝的是,最快的方法不是我們理論上預期的方法。

我嘗試了一些代碼如下。

#include <cstring>
#include <iostream>
#include <string>
#include <chrono>

using std::string;
using std::chrono::system_clock;

inline void mycopy( double* a, double* b, size_t s ) {
   while ( s > 0 ) {
      *a++ = *b++;
      --s;
   }
};

// to make sure that every bits have been changed
bool assertAllTrue( unsigned char* a, size_t s ) {
   unsigned char v = 0xFF;
   while ( s > 0 ) {
      v &= *a++;
      --s;
   }
   return v == 0xFF;
};

int main( int argc, char** argv ) {
   alignas( 16 ) char bufA[512], bufB[512];
   memset( bufB, 0xFF, 512 );  // to prevent strncpy from stoping prematurely
   system_clock::time_point startT;

   memset( bufA, 0, sizeof( bufA ) );
   startT = system_clock::now();
   for ( int i = 0; i < 1024 * 1024; ++i )
      strncpy( bufA, bufB, sizeof( bufA ) );
   std::cout << "strncpy:" << ( system_clock::now() - startT ).count()
             << ", AllTrue:" << std::boolalpha
             << assertAllTrue( ( unsigned char* )bufA, sizeof( bufA ) )
             << std::endl;

   memset( bufA, 0, sizeof( bufA ) );
   startT = system_clock::now();
   for ( int i = 0; i < 1024 * 1024; ++i )
      memcpy( bufA, bufB, sizeof( bufA ) );
   std::cout << "memcpy:" << ( system_clock::now() - startT ).count()
             << ", AllTrue:" << std::boolalpha
             << assertAllTrue( ( unsigned char* )bufA, sizeof( bufA ) )
             << std::endl;

   memset( bufA, 0, sizeof( bufA ) );
   startT = system_clock::now();
   for ( int i = 0; i < 1024 * 1024; ++i )
      memmove( bufA, bufB, sizeof( bufA ) );
   std::cout << "memmove:" << ( system_clock::now() - startT ).count()
             << ", AllTrue:" << std::boolalpha
             << assertAllTrue( ( unsigned char* )bufA, sizeof( bufA ) )
             << std::endl;

   memset( bufA, 0, sizeof( bufA ) );
   startT = system_clock::now();
   for ( int i = 0; i < 1024 * 1024; ++i )
      mycopy( ( double* )bufA, ( double* )bufB, sizeof( bufA ) / sizeof( double ) );
   std::cout << "mycopy:" << ( system_clock::now() - startT ).count()
             << ", AllTrue:" << std::boolalpha
             << assertAllTrue( ( unsigned char* )bufA, sizeof( bufA ) )
             << std::endl;

   return EXIT_SUCCESS;
}

結果(許多類似結果之一):

strncpy:52840919,AllTrue:true

memcpy:57630499,AllTrue:true

memmove:57536472,AllTrue:true

mycopy:57577863,AllTrue:true

看起來像:

  1. memcpy,memmove和我自己的方法有類似的結果;
  2. strncpy做什么魔法,所以它比memcpy更快?

有趣嗎?

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

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