代码执行的效率

代码执行的效率

在《性能调优攻略》里,我说过,要调优性须要找到程序中的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程序

(全文完)