[英]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函數如memcmp
, memcpy
, strcpu
, memset
等在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
看起來像:
有趣嗎?
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.