Re: [問題] 怎麼提高效率?

看板C_and_CPP作者 (藍影)時間12年前 (2012/03/18 23:43), 編輯推噓11(11022)
留言33則, 8人參與, 最新討論串2/3 (看更多)
※ 引述《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

03/18 23:47, , 1F
不好意思 我權限已經開了QQ
03/18 23:47, 1F

03/18 23:47, , 2F
感謝你回這麼多QQ
03/18 23:47, 2F

03/18 23:57, , 3F
t大你好 你cpy3的程式 會不會有我 17758篇的問題呢 某些平台
03/18 23:57, 3F

03/18 23:57, , 4F
其實我不是很確定我之前那問題到底是怎麼發生的@@
03/18 23:57, 4F

03/18 23:59, , 5F
有沒有如果要改善一個300行以上的程式碼,首先要檢查
03/18 23:59, 5F

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

03/19 00:01, , 7F
不會吧,17758那篇我不是把原因都講明了嗎
03/19 00:01, 7F

03/19 00:02, , 8F
有~~但我覺得跟他cpy3類似的寫法 轉型不會遇到我那問題嗎
03/19 00:02, 8F
考慮移植與速度問題後, memcpy 流傳的 code 基本上就是 cpy3, cpy4 / cpy5 目前我沒看別人用過,有問題的話也只可能是它們而已 (目前我是看不出有什麼問題),「轉型」的話... 我想是在 caller 那裡處理的, 和 memcpy 本身沒太大關係吧 Orz

03/19 00:31, , 9F
會 XD
03/19 00:31, 9F

03/19 00:32, , 10F
+ size 我記得是在標準之內的 (就是所謂的「最後一個元素
03/19 00:32, 10F

03/19 00:33, , 11F
之後的那一格」 標準記得有規定這是 OK 的)
03/19 00:33, 11F

03/19 00:58, , 12F
@L大:我只是想到了之前 F 大的文章 - #1EB157xt
03/19 00:58, 12F

03/19 00:58, , 13F
裡面推翻了 ptr-=4 這種寫法,雖我不知出自哪條款 Orz
03/19 00:58, 13F

03/19 01:00, , 14F
文章給錯, 補上 #1EAjP7Qe
03/19 01:00, 14F

03/19 01:50, , 15F
我也翻到了: #1E1omrqj 這篇有引標準說到這回事
03/19 01:50, 15F

03/19 01:51, , 16F
在一個陣列裡指來指去的指標可以指到最後一個元素的後一格
03/19 01:51, 16F

03/19 01:52, , 17F
在這範圍內的話都是 OK 的 所以只要 size 真是陣列大小的話
03/19 01:52, 17F

03/19 01:52, , 18F
source + size 是沒問題的
03/19 01:52, 18F

03/19 01:53, , 19F
你那程式的問題點反而在於 void * 不能拿來做指標運算...
03/19 01:53, 19F

03/19 02:17, , 20F
謝謝L大,進一步請教,我在調用void* 前都有先轉型,不
03/19 02:17, 20F

03/19 02:17, , 21F
知您指的是哪一段?
03/19 02:17, 21F

03/19 03:26, , 22F
啊, 那就是我猛一看看錯了而已 XD
03/19 03:26, 22F

03/19 17:09, , 23F
老一點的話 Duff's device應該也算加速方法吧...
03/19 17:09, 23F

03/19 18:03, , 24F
Duff's device 只是迴圈展開的技巧而已...
03/19 18:03, 24F

03/19 23:43, , 25F
這篇要m起來
03/19 23:43, 25F

03/20 14:54, , 26F
這篇棒!
03/20 14:54, 26F

03/21 19:18, , 27F
我做了一些測試和調整 發現memcpy最快@@...
03/21 19:18, 27F

03/21 19:20, , 28F
http://codepad.org/hqe00v50 好像內容一致的會做比較快..
03/21 19:20, 28F

03/21 19:31, , 29F
O3時 memcpy(370ms) other(570ms)吧
03/21 19:31, 29F
較完整之測試結果及其說明,已於文末附上,參閱。

03/21 20:33, , 30F
我找個時間修文,有些內容要更改,確實是 memcpy 最快.
03/21 20:33, 30F

03/21 20:37, , 31F
內容都一樣的話這情況應比較少見,所以沒那麼做.但之前
03/21 20:37, 31F

03/21 20:37, , 32F
測不合理的地方在於,memcpy不該再包一層 function.
03/21 20:37, 32F

03/21 20:38, , 33F
那層function拿掉後,memcpy就真的是最快了。
03/21 20:38, 33F
※ 編輯: tropical72 來自: 180.177.76.161 (03/22 03:05)
文章代碼(AID): #1FPWAHqo (C_and_CPP)
討論串 (同標題文章)
文章代碼(AID): #1FPWAHqo (C_and_CPP)