Re: [心得] 記憶體效能瓶頸

作者: fo40225   2016-01-02 15:06:59
※ 引述《erspicu (.)》之銘言:
: 全code....
: http://paste.ofcode.org/3bw8ufpUwuPqT4xNfwUScZH
: 你覺得在你預設的猜測中
: a.
: color = (uint)((0xff << 24) | ((rgb555 & 0x1f) << 19) | ((rgb555 & 0x3e0)
: << 6) | ((rgb555 & 0x7c00) >> 7));
: b.
: color = rgb555_Table[rgb555 ];
: 你會覺得哪個速度比較快????
: 一個是好幾次的bitwise計算算出結果..
: 一個是用index撈取array一個步驟
: 答案是 a 比較快... 不管是debug模式下或是一般模式
: (現在一般電腦的情況...cpu效能超高,一般記憶體沒追上cpu速度)
: 只是一般模式a快得更多
: 而pointer的寫法 debug模式下,快一般array的操作方式一點,一般模式,
: 用指標跟ARRAY速度差不多(應該是一樣)....
: pointer在c#好像不只一種方式操作
測試環境為 VS2015 .net 4.6.1 Release build 64位元執行模式
直接觀察反組譯碼
在原測試code中第51行
color = (uint)((0xff << 24) | ((rgb555 & 0x1f) << 19) | ((rgb555 & 0x3e0)
<< 6) | ((rgb555 & 0x7c00) >> 7));
這句只會執行一次(最後一次) 而不是10000*65535次
(反而第50行 rgb555 = (ushort)(i & 0xffff);
老老實實的執行了10000*65535次 優化的邏輯真奇怪)
而array只會檢查i有沒有超過陣列界限65535次 不會取值
pointer 則是CLR的bug
https://github.com/dotnet/coreclr/issues/2480
每次存取fixed的pointer時 都重新從local variables載入到CPU register
(暫時的解法是把pointer複製出來一份)
所以結果為 37X 19X 55X ms
====
測試code
http://paste.ofcode.org/d8tZ28wfK3iA5Mi3kLuvAR
不確定在原測試code 63行有沒有寫錯
color = rgb555_Table_pointer[i];
測試code通通改成用
color = rgb555_Table_pointer[rgb555];
註: GC.KeepAlive() 是拿來模擬一個method call 避免取值賦值被優化掉
其方法內容是空白 如果不呼叫GC.KeepAlive() 而自己寫空方法的話 一樣會被優化掉
http://referencesource.microsoft.com/#mscorlib/system/gc.cs,18b31b2edcc0f711
在加上GC.KeepAlive(color); 的狀況下
Array 32XX ms
bitwise 34XX ms
pointer 30XX ms
而這個測試也順便測試JIT warm up 的影響
以及傳說 "int的操作有特別優化" 這兩件事
看起來是沒什麼影響
====
: 參考
: http://nbsoftsolutions.com/blog/high-performance-unsafe-c-code-is-a-lie
這篇文章剛好挑到一個在.net中極度優化的類別 string
所以會得到使用指標沒有什麼加速效果的結論
: stringBuilder.Append((char)currentByte);
內部為char array pointer操作
http://referencesource.microsoft.com/#mscorlib/system/text/
stringbuilder.cs.html,a2e7c78d85807da5
: stringBuilder.ToString()
內部為pointer操作
http://referencesource.microsoft.com/#mscorlib/system/text/
stringbuilder.cs,5a97da49a158a3c9
: charBuffer[index++] = (char)currentByte;
: new string(charBuffer, 0, index);
: *bytePtr++ = currentByte;
: new string((sbyte*)byteArray);
: charPtr++ = (char)currentByte;
: new string(charPtr);
string的建構子內部均為為C++實作
以String(char [] value)為例
http://referencesource.microsoft.com/#mscorlib/system/
string.cs,ec408439366007e6
https://github.com/dotnet/coreclr/blob/master/src/vm/ecalllist.h#L220
https://github.com/dotnet/coreclr/blob/master/src/
classlibnative/bcltype/stringnative.cpp#L99
https://github.com/dotnet/coreclr/blob/master/src/vm/object.cpp#L2086
測試出來的結果相當是正常的 如果有比較慢 有可能是做了多餘的轉型導致
網路上也有使用unsafe大幅加速的例子 可參考
http://blog.darkthread.net/post-2010-06-19-use-unsafe.aspx
作者: Litfal (Litfal)   2016-01-02 15:28:00
64位元環境的確會有這個短迴圈只跑一次的問題,我也不知道是神優化還是BUG,用32BIT或把color提到class field才能
作者: fo40225   2016-01-02 15:38:00
剛測試 32位元一樣只跑一次 class field就不會只有一次了
作者: erspicu (.)   2016-01-02 18:24:00
一直覺得優化原則.JIT 若沒摸都反組譯那層 被包著沒辦法深入看進去的話 一切都很謎樣... 可能真的要來學了

Links booklink

Contact Us: admin [ a t ] ucptt.com