有關程式碼執行效率提升的小例子
要調優性需要找到程式中的Hotspot,也就是被呼叫最多的地方,這種地方,只要你能最佳化一點點,你的效能就會有質的提高。在這裡我給大家舉三個關於程式碼執行效率的例子(它們都來自於網上)
第一個例子
PHP中Getter和Setter的效率(來源reddit)
這個例子比較簡單,你可以跳過。
考慮下面的PHP程式碼:我們可看到,使用Getter/Setter的方式,效能要比直接讀寫成員變數要差一倍以上。
這個並沒有什麼稀,因為有函式呼叫的開銷,函式呼叫需要壓棧出棧,需要傳值,有時還要需要中斷,要乾的事太多了。所以,程式碼多了,效率自然就慢了。所有的語言都這個德行,這就是為什麼C++要引入inline的原因。而且Java在開啟最佳化的時候也可以最佳化之。但是對於動態語言來說,這個事就變得有點困難了。
你可能會以為使用下面的程式碼(Magic Function)會好一些,但實際其效能更差。
1 class dog {
2 private $_name = "";
3 function __set($property,$value) {
4 if($property == 'name') $this->_name = $value;
5 }
6 function __get($property) {
7 if($property == 'name') return $this->_name;
8 }
9 }
動態語言的效率從來都是一個問題,如果你需要PHP有更好的效能,你可能需要使用FaceBook的HipHop來把PHP編譯成C語言。
第二個例子
為什麼Python程式在函式內執行得更快?(來源StackOverflow)
考慮下面的程式碼,一個在函式體內,一個是全域性的程式碼。
函式內的程式碼執行效率為 1.8s
1 def main():
2 for i in xrange(10**8):
3 pass
4 main()
函式體外的程式碼執行效率為 4.5s
1 for i in xrange(10**8):
2 pass
不用太糾結時間,只是一個示例,我們可以看到效率查得很多。為什麼會這樣呢?我們使用 dis module 反彙編函式體內的bytecode 程式碼,使用 compile builtin 反彙編全域性bytecode,我們可以看到下面的反彙編(注意我高亮的地方)
MAIN函式反彙編
1 13 FOR_ITER 6 (to 22)
2 16 STORE_FAST 1 (i)
3 19 JUMP_ABSOLUTE 13
全域性程式碼
1 13 FOR_ITER 6 (to 22)
2 16 STORE_NAME 1 (i)
3 19 JUMP_ABSOLUTE 13
我們可以看到,差別就是 STORE_FAST 和 STORE_NAME,前者比後者快很多。所以,在全域性程式碼中,變數i成了一個全域性變數,而函式中的i是放在本地變數表中,所以在全域性變數表中查詢變數就慢很多。如果你在main函式中宣告global i 那麼效率也就下來了。原因是,本地變數是存在一個陣列中(直到),用一個整型常量去訪問,而全域性變數存在一個dictionary中,查詢很慢。
(注:在C/C++中,這個不是一個問題)
第三個例子
為什麼排好序的資料在遍歷時會更快?(來源StackOverflow)
參看如下C/C++的程式碼:
1 for (unsigned i = 0; i < 100000; ++i) {
2 // primary loop
3 for (unsigned j = 0; j < arraySize; ++j) {
4 if (data[j] >= 128)
5 sum += data[j];
6 }
7 }
如果你的data陣列是排好序的,那麼效能是1.93s,如果沒有排序,效能為11.54秒。差5倍多。無論是C/C++/Java,或是別的什麼語言都基本上一樣。
這個問題的原因是—— branch prediction (分支預判)偉大的stackoverflow給了一個非常不錯的解釋。
考慮我們一個鐵路分叉,當我們的列車來的時候, 扳道員知道分個分叉通往哪,但不知道這個列車要去哪兒,司機知道要去哪,但是不知道走哪條分叉。所以,我們需要讓列車停下來,然後司機和扳道員溝通一下。這樣的效能太差了。
所以,我們可以最佳化一下,那就是猜,我們至少有50%的機率猜對,如果猜對了,火車行駛效能巨高,猜錯了,就得讓火車退回來。如果我猜對的機率高,那麼,我們的效能就會高,否則老是猜錯了,效能就很差。
我們的if-else 就像這個鐵路分叉一樣,下面紅箭頭所指的就是搬道器。
那麼,我們的搬道器是怎麼預判的呢?就是使用過去的歷史資料,如果歷史資料有90%以上的走左邊,那麼就走左邊。所以,我們排好序的資料就更容易猜得對。
排好序的
1 T = 走分支(條件表示式為true)
2 N = 不走分支(條件表示式為false)
3
4 data[] = 0, 1, 2, 3, 4, ... 126, 127, 128, 129, 130, ... 250, 251, 252, ...
5 branch = N N N N N ... N N T T T ... T T T ...
6
7 = NNNNNNNNNNNN ... NNNNNNNTTTTTTTTT ... TTTTTTTTTT (easy to predict)
未排序的
1 data[] = 226, 185, 125, 158, 198, 144, 217, 79, 202, 118, 14, 150, 177, 182, 133, ...
2 branch = T, T, N, T, T, T, T, N, T, N, N, T, T, T, N ...
3
4 = TTNTTTTNTNNTTTN ... (completely random - hard to predict)
從上面我們可以看到,排好序的資料更容易預測分支。
對此,那我們怎麼辦?我們需要在這種迴圈中除去if-else語句。比如:
我們把條件語句:
1 if (data[j] >= 128)
2 sum += data[j];
變成:
1 int t = (data[j] - 128) >> 31;
2 sum += ~t & data[j];
“沒有分叉”的效能基本上和“排好序有分支”一個樣,無論是C/C++,還是Java。
注:在GCC下,如果你使用 -O3 or -ftree-vectorize 編譯引數,GCC會幫你最佳化分叉語句為無分叉語句。VC++2010沒有這個功能。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31365439/viewspace-2682858/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 提升專案執行效率的關鍵利器
- 提高程式碼的執行效率(1)
- 小程式助力提升運維效率運維
- 效能基準DevOps之如何提升指令碼執行效率dev指令碼
- 教小師妹學多執行緒,一個有深度的例子!執行緒
- 使用小程式助力提升運維效率!運維
- 有關mysql中ROW_COUNT()的小例子MySql
- Java效能優化:教你提高程式碼執行的效率Java優化
- 提高Python執行效率的5個小技巧!Python
- 提升開發效率,小程式容器來幫你
- 執行效率高的程式碼-可以這樣寫出來~
- matlab: 檢查程式執行效率Matlab
- 手把手提高基礎程式碼執行效率
- App Cleaner & Uninstaller:全面最佳化Mac,提升執行效率APPMac
- Windows10提升效率小TipsWindows
- 關於程式碼如何執行的五個問題
- 一套程式碼小程式&Web&Native執行的探索(2)Web
- 程式池、執行緒池效率測試執行緒
- 執行緒與程式之間有什麼關係?Linux執行緒與程式有什麼區別?執行緒Linux
- 執行緒池關閉的小結執行緒
- 實現手機直播原始碼中兩個執行緒依次執行的相關程式碼原始碼執行緒
- switch執行效率
- 程式碼執行
- VSCode使用技巧,程式碼編寫效率提升2倍以上!VSCode
- Tech Talk · 雲技術有話聊 | 基於低程式碼的資料開發如何提升效率?
- 程式執行緒篇——總結與提升執行緒
- 《程式設計珠璣》程式碼之路14:兩個不會演算法也能把效率提升4倍的小套路程式設計演算法
- 助力提升移動研發效率的小程式容器技術,你瞭解嗎?
- 解析小程式雙執行緒技術,助力移動應用體驗提升執行緒
- 多執行緒程式是如何執行程式碼的?執行緒行程
- JavaScript --有關提升JavaScript
- javascript執行緒及與執行緒有關的效能優化JavaScript執行緒優化
- 【python小例子】小例子拾憶Python
- 一個SystemC執行緒與SystemVerilog執行緒通訊的例子執行緒
- 如何藉助小程式容器與前端中介軟體提升開發效率前端
- [求指導] 如何通過程式碼分析一個查詢語句的執行效率
- JavaScript的程式碼執行機制JavaScript
- 程式碼是怎麼執行的?