代碼執行的效率
在《性能調優攻略》里,我說過,要調優性需要找到程序中的Hotspot,也就是被調用最多的地方,這種地方,只要你能優化一點點,你的性能就會有質的提高。在這里我給大家舉三個關于代碼執行效率的例子(它們都來自于網上)
第一個例子
PHP中Getter和Setter的效率(來源reddit)
這個例子比較簡單,你可以跳過。
考慮下面的PHP代碼:我們可看到,使用Getter/Setter的方式,性能要比直接讀寫成員變量要差一倍以上。
<?php //dog_naive.phpclass dog { public $name = ""; public function setName($name) { $this->name = $name; } public function getName() { return $this->name; } } $rover = new dog(); //通過Getter/Setter方式 for ($x=0; $x<10; $x++) { $t = microtime(true); for ($i=0; $i<1000000; $i++) { $rover->setName("rover"); $n = $rover->getName(); } echo microtime(true) - $t; echo "\n"; } //直接存取變量方式 for ($x=0; $x<10; $x++) { $t = microtime(true); for($i=0; $i<1000000; $i++) { $rover->name = "rover"; $n = $rover->name; } echo microtime(true) - $t; echo "\n"; }
?></pre>
這個并沒有什么稀,因為有函數調用的開銷,函數調用需要壓棧出棧,需要傳值,有時還要需要中斷,要干的事太多了。所以,代碼多了,效率自然就慢了。所有的語言都這個德行,這就是為什么C++要引入inline的原因。而且Java在打開優化的時候也可以優化之。但是對于動態語言來說,這個事就變得有點困難了。
你可能會以為使用下面的代碼(Magic Function)會好一些,但實際其性能更差。
class dog { private $_name = ""; function __set($property,$value) { if($property == 'name') $this->_name = $value; } function __get($property) { if($property == 'name') return $this->_name; } }動態語言的效率從來都是一個問題,如果你需要PHP有更好的性能,你可能需要使用非死book的HipHop來把PHP編譯成C語言。
第二個例子
為什么Python程序在函數內執行得更快?(來源StackOverflow)
考慮下面的代碼,一個在函數體內,一個是全局的代碼。
函數內的代碼執行效率為 1.8s
def main(): for i in xrange(10**8): pass main()函數體外的代碼執行效率為 4.5s
for i in xrange(10**8): pass不用太糾結時間,只是一個示例,我們可以看到效率查得很多。為什么會這樣呢?我們使用
dis
module 反匯編函數體內的bytecode 代碼,使用compile
builtin 反匯編全局bytecode,我們可以看到下面的反匯編(注意我高亮的地方)13 FOR_ITER 6 (to 22) 16 STORE_FAST 1 (i) 19 JUMP_ABSOLUTE 1313 FOR_ITER 6 (to 22) 16 STORE_NAME 1 (i) 19 JUMP_ABSOLUTE 13我們可以看到,差別就是
STORE_FAST
和STORE_NAME,前者比后者快很多。所以,在全局代碼中,變量i成了一個全局變量,而函數中的i是放在本地變量表中,所以在全局變量表中查找變量就慢很多。如果你在main函數中聲明global i 那么效率也就下來了。
原因是,本地變量是存在一個數組中(直到),用一個整型常量去訪問,而全局變量存在一個dictionary中,查詢很慢。
(注:在
C/C++中,這個不是一個問題)第三個例子
為什么排好序的數據在遍歷時會更快?(來源StackOverflow)
參看如下C/C++的代碼:
for (unsigned i = 0; i < 100000; ++i) { // primary loop for (unsigned j = 0; j < arraySize; ++j) { if (data[j] >= 128) sum += data[j]; } }如果你的data數組是排好序的,那么性能是1.93s,如果沒有排序,性能為11.54秒。差5倍多。無論是C/C++/Java,或是別的什么語言都基本上一樣。
這個問題的原因是—— branch prediction (分支預判)傳大的stackoverflow給了一個非常不錯的解釋。
考慮我們一個鐵路分叉,當我們的列車來的時候, 我們不知道這個列車要去哪兒,司機知道要去哪,但是不知道走哪條路。所以,我們需要讓列車停下來,然后司機和扳道員溝通一下。這樣的性能太差了。
所以,我們可以優化一下,那就是猜,我們至少有50%的概率猜對,如果猜對了,火車行駛性能巨高,猜錯了,就得讓火車退回來。如果我猜對的概率高,那么,我們的性能就會高,否則老是猜錯了,性能就很差。
Image by Mecanismo, from Wikimedia Commons:http://commons.wikimedia.org/wiki/File:Entroncamento_do_Transpraia.JPG
我們的if-else 就像這個鐵路分叉一樣,下面紅箭頭所指的就是搬道器。
那么,我們的搬道器是怎么預判的呢?就是使用過去的歷史數據,如果歷史數據有90%以上的走左邊,那么就走左邊。所以,我們排好序的數據就更容易猜得對。
T = 走分支(條件表達式為true) N = 不走分支(條件表達式為false)data[] = 0, 1, 2, 3, 4, ... 126, 127, 128, 129, 130, ... 250, 251, 252, ... branch = N N N N N ... N N T T T ... T T T ...
= NNNNNNNNNNNN ... NNNNNNNTTTTTTTTT ... TTTTTTTTTT (easy to predict)</pre>
data[] = 226, 185, 125, 158, 198, 144, 217, 79, 202, 118, 14, 150, 177, 182, 133, ... branch = T, T, N, T, T, T, T, N, T, N, N, T, T, T, N ...= TTNTTTTNTNNTTTN ... (completely random - hard to predict)</pre>
從上面我們可以看到,排好序的數據更容易預測分支。
對此,那我們怎么辦?我們需要在這種循環中除去if-else語句。比如:
我們把條件語句:
if (data[j] >= 128) sum += data[j];變成:
int t = (data[j] - 128) >> 31; sum += ~t & data[j];“沒有分叉”的性能基本上和“排好序有分支”一個樣,無論是C/C++,還是Java。
注:在GCC下,如果你使用
</blockquote>-O3
or-ftree-vectorize
編譯參數,GCC會幫你優化分叉語句為無分叉語句。VC++2010沒有這個功能。最后,推薦大家一個網站——Google Speed,網站上的有一些教程告訴你如何寫出更快的Web程序。
(全文完)
(轉載本站文章請注明作者和出處 酷殼 – CoolShell.cn ,請勿用于任何商業用途)