[爆卦]memcpy實作是什麼?優點缺點精華區懶人包

為什麼這篇memcpy實作鄉民發文收入到精華區:因為在memcpy實作這個討論話題中,有許多相關的文章在討論,這篇最有參考價值!作者tropical72 (藍影)看板C_and_CPP標題Re: [問題] 怎麼提高效率?時間Su...


※ 引述《heymei0421 (heymei)》之銘言:
: 小弟目前用ubuntu為作業系統
: 用google所提供的工具來量測performance
: 好不容易搞了一個下午 總算把程式中各函式所執行的時間百分比弄出來
: 如下圖:
: http://ppt.cc/ueyv

我看不到這東西,請釋放權限。

: void
: my_memcpy (void *target, void *source, int size)
: {
: int i;
: unsigned char *target_ptr = target;
: unsigned char *source_ptr = source;
: for (i = 0; i < size; i++)
: {
: *(target_ptr + i) = *(source_ptr + i);
: }
: }

如果你可以用 memcpy 的話就用它,它的速度正常的話會比自己寫快上2~3倍不是問題,
它放在 memory.h / string.h 裡面,不行調用的話才有必要自己寫。

一般而言在寫低階動作時,若「去掉 compiler 優化能力」,有幾個部份是值得注意的,
(i) 盡可能使用前置
(ii) 另一為盡可能減少陣列索引之計算
(iii) 程式碼可以展開的話就盡可能展開 (很可笑對吧 ? 但它是事實..)

所以你的程式碼暫修如下

typedef unsigned char byte;
void cpy1(void* target, void* source, int size)
{
byte *Src=(byte*)source;
byte *Des=(byte*)target;
while(size) {
*Des++ = *Src++;
--size;
}
}

這樣就避開了計算 Des[i] / Src[i] 所需時間,如果要再快一點點的話,
「考慮」要不要弄個非標準的做法。

void cpy2(void* target, void* source, int size)
{
byte *Src = (byte*)source;
byte *Des = (byte*)target;
byte *Src_End = (byte*)source + size;

while(Src!=Src_End) /* 使用比較運算子取代了遞減運算子 */
*Des++ = *Src++;
}

非標準的地方在於 byte *Src_End = (byte*)source + size;
正常而言,指標 + size 這動作並不保證,但大多 compiler (一些面試題也基於此假設)
是從 source 移動 size 個 byte 大小。會不會更快不一定,
要去查「比較」和「遞減」所使用的週期數。

截至目前為止,其實改的都只是小部份,但有個較大的部份沒改到,
如果可以一次 4 bytes / 8 bytes 複制,速度會更快,
多出來的部份再慢慢 1 bytes / 1 bytes 複制。
這裡演示基於標準作法,要非標準的作法可再自己嚐試

void cpy3(void* target, void* source, size_t size)
{
word *Wsrc_s = (word*)source;
word *Wdes_s = (word*)target;
byte *Bsrc_s;
byte *Bdes_s;
// 處理 4 bytes
while(size>4){
*Wdes_s++ = *Wsrc_s++;
size-=4;
}
// 處理 < 4 bytes
Bsrc_s = (byte*)(Wsrc_s);
Bdes_s = (byte*)(Wdes_s);
while(size) {
*Bdes_s++ = *Bsrc_s++;
--size;
}
}

基本上用到 4bytes 一次複製觀念速度就夠快了,
另外有個比較 kuso 的方式你可以參考
先建立一個 struct, 裡面直接丟 byte trunk[256], byte trunk[1024] 等
struct 做 assigned 時,裡面的 trunk 會直接複制,
但速度怎樣將會相依於 compiler 實作之能力,會不會比較快不得而知,
但以我手中 vc2010 , debug mode 下測,這方式是最快的 (開 256),
在 size 較大情況下,速度比你原本方式快 5 倍以上 (看 N 值),
但最終還是打不過 memcpy (記得它 "應" 是用組語寫的!)

完整的測試程式碼參考如連結 http://ppt.cc/a;Bv
結果如下參考。

N 787654321
cpy1 : 453 arr2=arr1 (check) --> memcpy
cpy2 : 3610 arr2=arr1 (check) --> 逐一 copy
cpy3 : 938 arr2=arr1 (check) --> 4 bytes 處理
cpy4 : 531 arr2=arr1 (check) --> struct 256 bytes 處理
cpy5 : 500 arr2=arr1 (check) --> struct 1024 + 256 + 4 bytes 處理。

vc 開 option 測時會很不準,懶得再用 gcc 測 (其實也懶得再看 .asm)
其實上面的 cpy4 / cpy5 還是有可改善空間,希望這些意見能給你一些幫助。

--
2012.3.19 補充

(1) struct 裡面可以用 unsigned int trunk[] 取代 unsigned char turnk[],
速度理應會更快。
(2) struct trunk[size], size 大小其實可以調大一點,上面 1024 + 256 其實
是不智的二個數字 (因為 256 最多也跑 4 次而已),可以試試 4096 + 256,
甚至試 8192 + 256, 最大調多大為佳, 和硬體 cache size, compiler 對於
struct member assigned 實作相依。
(3) 開 O2/O3 後 (gcc,vc都一樣), 其實逐一 assigned 和 memcpy 差不了多了。

2012.3.22 補充

(1) trace 結果發現 struct in C 有幾個特性, 一般 compiler 實作
struct S mem={0},其實是呼叫 memset。

(2) 合理原因猜測,以 struct 包 array, 做 assigned 動作時,
實際上 compiler 是自己再去呼叫 memcpy 出來做,
故其實和直接呼叫 memcpy 沒什麼太大差別。

(3) 由於測時到近 1 G 時,測時時間也不到 1 秒,精度測時使用 clock()
過於不精準,換較高精度做計數。同時測時時,memcpy 不該再另包一
層 function。另會看 asm 的人會發現,struct 包 array 方式,
翻成組語後實際上還是在調用 mov 這東西,沒什麼太多技巧。

補上較完整之結果與測試碼。 http://ppt.cc/5sid xmemcpy2.rar 。
心得在裡面的文字檔有說明, 特別是關於 duff's device 部份。

(4) 強調,本文並沒有針對 alignment 問題做處理,意思是如果沒 alignment
的話,程式碼會出包。即使不出包,效能也會變差。
--
我知道 ~ 但別說出來 ,
說出來讓人感到特別難過...

--
※ 發信站: 批踢踢實業坊(ptt.cc)
◆ From: 123.195.165.40
heymei0421:不好意思 我權限已經開了QQ 03/18 23:47
heymei0421:感謝你回這麼多QQ 03/18 23:47
QQ29:t大你好 你cpy3的程式 會不會有我 17758篇的問題呢 某些平台 03/18 23:57
QQ29:其實我不是很確定我之前那問題到底是怎麼發生的@@ 03/18 23:57
heymei0421:有沒有如果要改善一個300行以上的程式碼,首先要檢查 03/18 23:59
heymei0421:可能要改的首要幾個地方有哪些ㄋ? 03/18 23:59

測 profile 我記得有兩套常被拿來用, vs 有 performance Tool,intel 有 VTune
如果都不會用的話~分析一下演算法本身能不能將複雜度降低,降不下來的話~
只有 300 行而已,將可以包出 function 的部份都用 clock() 做 wall time test,
不就知道效能卡在哪了嗎?話說剛 vc / gcc 開 O2 之後,其實你原本的寫法和
memcpy 根本差不了多少 (1G,859 / 1141),要再改善的話能力有限。

littleshan:不會吧,17758那篇我不是把原因都講明了嗎 03/19 00:01
QQ29:有~~但我覺得跟他cpy3類似的寫法 轉型不會遇到我那問題嗎 03/19 00:02

考慮移植與速度問題後, memcpy 流傳的 code 基本上就是 cpy3,
cpy4 / cpy5 目前我沒看別人用過,有問題的話也只可能是它們而已
(目前我是看不出有什麼問題),「轉型」的話... 我想是在 caller 那裡處理的,
和 memcpy 本身沒太大關係吧 Orz

littleshan:會 XD 03/19 00:31
LPH66:+ size 我記得是在標準之內的 (就是所謂的「最後一個元素 03/19 00:32
LPH66:之後的那一格」 標準記得有規定這是 OK 的) 03/19 00:33
tropical72:@L大:我只是想到了之前 F 大的文章 - #1EB157xt 03/19 00:58
tropical72:裡面推翻了 ptr-=4 這種寫法,雖我不知出自哪條款 Orz 03/19 00:58
tropical72:文章給錯, 補上 #1EAjP7Qe 03/19 01:00
LPH66:我也翻到了: #1E1omrqj 這篇有引標準說到這回事 03/19 01:50
LPH66:在一個陣列裡指來指去的指標可以指到最後一個元素的後一格 03/19 01:51
LPH66:在這範圍內的話都是 OK 的 所以只要 size 真是陣列大小的話 03/19 01:52
LPH66:source + size 是沒問題的 03/19 01:52
LPH66:你那程式的問題點反而在於 void * 不能拿來做指標運算... 03/19 01:53
tropical72:謝謝L大,進一步請教,我在調用void* 前都有先轉型,不 03/19 02:17
tropical72:知您指的是哪一段? 03/19 02:17
LPH66:啊, 那就是我猛一看看錯了而已 XD 03/19 03:26
firejox:老一點的話 Duff's device應該也算加速方法吧... 03/19 17:09
LPH66:Duff's device 只是迴圈展開的技巧而已... 03/19 18:03
tomnelson:這篇要m起來 03/19 23:43
yayarice:這篇棒! 03/20 14:54
firejox:我做了一些測試和調整 發現memcpy最快@@... 03/21 19:18
firejox:http://codepad.org/hqe00v50 好像內容一致的會做比較快.. 03/21 19:20
firejox:O3時 memcpy(370ms) other(570ms)吧 03/21 19:31

較完整之測試結果及其說明,已於文末附上,參閱。

tropical72:我找個時間修文,有些內容要更改,確實是 memcpy 最快. 03/21 20:33
tropical72:內容都一樣的話這情況應比較少見,所以沒那麼做.但之前 03/21 20:37
tropical72:測不合理的地方在於,memcpy不該再包一層 function. 03/21 20:37
tropical72:那層function拿掉後,memcpy就真的是最快了。 03/21 20:38
※ 編輯: tropical72 來自: 180.177.76.161 (03/22 03:05)

你可能也想看看

搜尋相關網站