代碼執行的效率

代碼執行的效率

在《性能調優攻略》裏,我說過,要調優性須要找到程序中的Hotspot,也就是被調用最多的地方,這種地方,只要你能優化一點點,你的性能就會有質的提升。在這裏我給你們舉三個關於代碼執行效率的例子(它們都來自於網上)php

第一個例子

PHP中Getter和Setter的效率來源reddithtml

這個例子比較簡單,你能夠跳過。python

考慮下面的PHP代碼:咱們可看到,使用Getter/Setter的方式,性能要比直接讀寫成員變量要差一倍以上。git

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?php
     //dog_naive.php
 
     class dog {
         public $name = "" ;
         public function setName( $name ) {
             $this -&gt;name = $name ;
         }
         public function getName() {
             return $this -&gt;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" ;
     }
?>

這個並無什麼稀,由於有函數調用的開銷,函數調用須要壓棧出棧,須要傳值,有時還要須要中斷,要乾的事太多了。因此,代碼多了,效率天然就慢了。全部的語言都這個德行,這就是爲何C++要引入inline的緣由。並且Java在打開優化的時候也能夠優化之。可是對於動態語言來講,這個事就變得有點困難了。github

 

你可能會覺得使用下面的代碼(Magic Function)會好一些,但實際其性能更差。shell

1
2
3
4
5
6
7
8
9
class dog {
     private $_name = "" ;
     function __set( $property , $value ) {
         if ( $property == 'name' ) $this ->_name = $value ;
     }
     function __get( $property ) {
         if ( $property == 'name' ) return $this ->_name;
     }
}

動態語言的效率歷來都是一個問題,若是你須要PHP有更好的性能,你可能須要使用FaceBook的HipHop來把PHP編譯成C語言。數組

第二個例子

爲何Python程序在函數內執行得更快?來源StackOverflowbash

考慮下面的代碼,一個在函數體內,一個是全局的代碼。dom

函數內的代碼執行效率爲 1.8s函數

1
2
3
4
def main():
     for i in xrange ( 10 * * 8 ):
         pass
main()

函數體外的代碼執行效率爲 4.5s

1
2
for i in xrange ( 10 * * 8 ):
     pass

不用太糾結時間,只是一個示例,咱們能夠看到效率查得不少。爲何會這樣呢?咱們使用 dis module 反彙編函數體內的bytecode 代碼,使用 compile builtin 反彙編全局bytecode,咱們能夠看到下面的反彙編(注意我高亮的地方)

Main函數反彙編
1
2
3
13 FOR_ITER                 6 (to 22)
16 STORE_FAST               1 (i)
19 JUMP_ABSOLUTE           13
全局代碼
1
2
3
13 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++的代碼:

1
2
3
4
5
6
7
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%以上的走左邊,那麼就走左邊。因此,咱們排好序的數據就更容易猜得對。

排好序的
1
2
3
4
5
6
7
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)
未排序的
1
2
3
4
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)

從上面咱們能夠看到,排好序的數據更容易預測分支。

對此,那咱們怎麼辦?咱們須要在這種循環中除去if-else語句。好比:

咱們把條件語句:

1
2
if (data[j] >= 128)
sum += data[j];

變成:

1
2
int t = (data[j] - 128) >> 31;
sum += ~t & data[j];

「沒有分叉」的性能基本上和「排好序有分支」一個樣,不管是C/C++,仍是Java。

注:在GCC下,若是你使用 -O3 or -ftree-vectorize 編譯參數,GCC會幫你優化分叉語句爲無分叉語句。VC++2010沒有這個功能。

最後,推薦你們一個網站——Google Speed,網站上的有一些教程告訴你如何寫出更快的Web程序

(全文完)

相關文章
相關標籤/搜索