寫代碼有時候和笃信宗教一樣,一旦信仰崩潰,是最難受的事情。早年我讀過雲風的一篇《VC 對 memcpy 的優化》,以及《Efficiency geek 2: copying data in C/C++, optimisation》,所以我是堅信很難能寫出比C運行時庫更快的memcpy的。但最近有兩個事情,讓我對這個堅信產生了懷疑。
第一個個是最近在看lz4的代碼,lz4可能是目前最快的內存壓縮算法,部分評測他比snappy還要快點(lz4的實現後面專文剖析)。研究他的代碼,發現他其中有個重要的和其他代碼不同地方就是他的內存拷貝采用的是一個宏,而不是使用memcpy。其內部直接使用uint64_t轉換指針進行拷貝賦值,個人估計這是他加快處理速度的一個地方。其拷貝代碼大意如下,其實現不在乎字長溢出的部分。
1 //你要保證dst有足夠的空間,其要求空間很可能比sz大,都是8字節補齊的。 2 #define ZEN_TEST_FAST_COPY(dst,src,sz) {\ 3 char *_cpy_dst = dst; \ 4 const char *_cpy_src = src; \ 5 size_t _cpy_size = sz;\ 6 do \ 7 { \ 8 ZBYTE_TO_UINT64(_cpy_dst) = ZBYTE_TO_UINT64(_cpy_src); \ 9 _cpy_dst += sizeof(uint64_t); \ 10 _cpy_src += sizeof(uint64_t); \ 11 }while( _cpy_size > sizeof(uint64_t) && (_cpy_size -= sizeof(uint64_t))); \ 12 }
第二個是看了一篇文章《哪個memcpy更快?》。發現裡面說Linux標准庫的memcpy比較不堪。這個有點顛覆了。原文代碼有個問題是,其在最開始對非取整的部分進行了操作。但如果memcpy的dst,src參數地址是對齊的,這樣明顯不利於加快速度。我改進的代碼如下:
1 void *ZEN_OS::fast_memcpy(void *dst, const void *src, size_t sz) 2 { 3 void *r = dst; 4 5 //先進行uint64_t長度的拷貝,一般而言,內存地址都是對齊的, 6 size_t n = sz & ~(sizeof(uint64_t) - 1); 7 uint64_t *src_u64 = (uint64_t *) src; 8 uint64_t *dst_u64 = (uint64_t *) dst; 9 10 while (n) 11 { 12 *dst_u64++ = *src_u64++; 13 n -= sizeof(uint64_t); 14 } 15 16 //將沒有非8字節字長取整的部分copy 17 n = sz & (sizeof(uint64_t) - 1); 18 uint8_t *src_u8 = (uint8_t *) src; 19 uint8_t *dst_u8 = (uint8_t *) dst; 20 while (n-- ) 21 { 22 (*dst_u8++ = *src_u8++); 23 } 24 25 return r; 26 }
文章代碼裡面還有個一個類似函數,區別在於,其在每次循環拷貝了2次uint64_t字長的數據。為了行文方便我們稱之為fast_memcpy[2]把。
1 while (n) 2 { 3 *dst_u64++ = *src_u64++; 4 *dst_u64++ = *src_u64++; 5 n -= sizeof(uint64_t)*2; 6 }
為了搞明白到底如何,只有自己測試一下。測試拷貝了8,16……64K,1M,4M字節的數據。在LINUX 64 (GCC 4.3 O3優化),Windows7 X64(Visual 2010 realse),Windwos7 win32(realse)的環境進行了測試,測試采用高精度定時器采集數據。。
第一組測試以字節對齊數據進行,無聊的數據就不貼了,直接上圖。如果把最慢的速度比作100%,其他人作為和他的相對比率繪制圖片,線條一直在下面的肯定更好一些:
Linux 64位下(GCC 4.3 O3優化),字節對齊情況下拷貝速度對比,如下圖,
Windows7 X64,Visaul C++ 2010 Realse, 字節對齊情況下拷貝速度,如下圖
看上面的比較圖片就會知道memcpy在對齊清下,memcpy在任何時候都是不錯的選擇。
但內存拷貝還有另外一種常見情況,就是字節無法對齊的情況,而lz4作為一個壓縮算法,可能恰恰要經常面對無法對齊的情況,所以我針對這種情況也做了一下測試。
Linux 64位下(GCC 4.3 O3優化),字節非對齊情況下拷貝速度,如下圖,
Windows7 X64,Visaul C++ 2010 Realse, 字節非對齊情況下拷貝速度,如下圖,宏的拷貝
在字節不對齊的情況下,並且拷貝的內存長度小於256字節,用8字節的賦值方式速度會稍微優於memcpy,這可能也是lz4采用這個方法的原因。如果拷貝的尺寸更大的不對齊時,memcpy還是更好的選擇。而考慮到lz4大部分情況面對的拷貝字節,應該小於256字節。所以他采用宏拷貝的方式理論上是可以獲得一些優勢。
結論:
在字節對齊的情況下,memcpy在任何時候幾乎都是最優的選擇。
確實有些方法在特定條件下比memcpy快一點。但如果你不知道如何選擇,默認還是選擇memcpy為好。同理也適應memset函數。
Windows 平台上,如果數據長度達到1M或者4M,Windows的下的表現要好於Linxu平台,特別是在64位的平台上。(當然Linux測試環境是虛擬機,是否有一定影響?),可能的原因為止,和frostburn掰扯估計Windows下指令優化也許有過GCC之處。
寫出比運行時庫memcpy更快函數,這本身就是一個比較扯淡的事情,你的對手有編譯器優化,代碼運行優化,指令優化等多種手段。《哪個memcpy更快?》一文應該是有缪誤的。當然他可能有他的背景(沒有開優化?),作者可能並沒有說清。
參考文檔以及背景閱讀:
《VC 對 memcpy 的優化》 雲風解釋的VC對於各種拷貝長度,編譯器做的優化。
《Efficiency geek 2: copying data in C/C++, optimisation》作者對一些Geek的方法做了評估,當然第一篇memset部分寫的更為詳細一些。有些Geek作法大家可以看看。《各種版本的memcpy(底層優化)》一問解釋一些數據拷貝的優化方法。《Optimizing Memcpy improves speed》這個裡面提到的memcpy也不是我們所說的C運行時庫的memcpy,但此文也解釋了一些的提速數據拷貝方法。
《哪個memcpy更快?》誤導我的文章,我認為他所述的東東應該有個特殊場景。或者他說的C庫的memcpy和我理解就不是一個東東?
《C/C++ tip: How to copy memory quickly》文章也說明了這個問題,他的結論和我們一樣。
【本文作者是雁渡寒潭,本著自由的精神,你可以在無盈利的情況完整轉載此文 檔,轉載時請附上BLOG鏈接:http://www.cnblogs.com/fullsail/,否則每字一元,每圖一百不講價。對Baidu文庫和360doc加價一倍】