陈皓:代码执行的效率

开发 开发工具
在《性能调优攻略》里,我说过,要调优性需要找到程序中的Hotspot,也就是被调用最多的地方,这种地方,只要你能优化一点点,你的性能就会有质的提高。在这里我给大家举三个关于代码执行效率的例子……

在《性能调优攻略》里,我说过,要调优性需要找到程序中的Hotspot,也就是被调用最多的地方,这种地方,只要你能优化一点点,你的性能就会有质的提高。在这里我给大家举三个关于代码执行效率的例子(它们都来自于网上)

第一个例子

PHP中Getter和Setter的效率来源reddit

这个例子比较简单,你可以跳过。

考虑下面的PHP代码:我们可看到,使用Getter/Setter的方式,性能要比直接读写成员变量要差一倍以上。

  1. <?php  
  2.     //dog_naive.php  
  3.    
  4.     class dog {  
  5.         public $name = "";  
  6.         public function setName($name) {  
  7.             $this-&gt;name = $name;  
  8.         }  
  9.         public function getName() {  
  10.             return $this-&gt;name;  
  11.         }  
  12.     }  
  13.    
  14.     $rover = new dog();  
  15.         //通过Getter/Setter方式  
  16.     for ($x=0; $x<10; $x++) {  
  17.         $t = microtime(true);  
  18.         for ($i=0; $i<1000000; $i++) {  
  19.             $rover->setName("rover");  
  20.             $n = $rover->getName();  
  21.         }  
  22.         echo microtime(true) - $t;  
  23.         echo "\n";  
  24.     }  
  25.         //直接存取变量方式  
  26.         for ($x=0; $x<10; $x++) {  
  27.         $t = microtime(true);  
  28.         for($i=0; $i<1000000; $i++) {  
  29.             $rover->name = "rover";  
  30.             $n = $rover->name;  
  31.         }  
  32.         echo microtime(true) - $t;  
  33.         echo "\n";  
  34.     }  
  35. ?> 

这个并没有什么稀,因为有函数调用的开销,函数调用需要压栈出栈,需要传值,有时还要需要中断,要干的事太多了。所以,代码多了,效率自然就慢了。所有的语言都这个德行,这就是为什么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.     }  

动态语言的效率从来都是一个问题,如果你需要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.     }  

如果你的data数组是排好序的,那么性能是1.93s,如果没有排序,性能为11.54秒。差5倍多。无论是C/C++/Java,或是别的什么语言都基本上一样。

这个问题的原因是—— branch prediction (分支预判)伟大的stackoverflow给了一个非常不错的解释。

考虑我们一个铁路分叉,当我们的列车来的时候, 扳道员知道分个分叉通往哪,但不知道这个列车要去哪儿,司机知道要去哪,但是不知道走哪条分叉。所以,我们需要让列车停下来,然后司机和扳道员沟通一下。这样的性能太差了。

所以,我们可以优化一下,那就是猜,我们至少有50%的概率猜对,如果猜对了,火车行驶性能巨高,猜错了,就得让火车退回来。如果我猜对的概率高,那么,我们的性能就会高,否则老是猜错了,性能就很差。

[[84577]]

Image by Mecanismo, from Wikimedia Commons:http://commons.wikimedia.org/wiki/File:Entroncamento_do_Transpraia.JPG

我们的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没有这个功能。

最后,推荐大家一个网站——Google Speed,网站上的有一些教程告诉你如何写出更快的Web程序

(全文完)

原文链接:http://coolshell.cn/articles/7886.html

责任编辑:林师授 来源: 酷壳
相关推荐

2014-06-12 08:53:01

团队团队效率

2013-07-25 10:28:46

加班工作效率职场

2012-06-21 09:43:45

2014-02-24 10:45:00

2012-02-02 10:35:12

C++

2012-07-25 10:16:59

2012-04-27 10:24:07

2012-09-03 13:51:43

测试软件测试单元测试

2014-04-15 10:13:01

2012-08-02 09:36:58

fork面试题

2021-12-22 10:49:42

架构运维技术

2012-10-15 16:13:29

2012年度IT博客大陈皓

2012-01-16 09:58:26

2012-08-16 13:31:06

陈皓架构师

2014-07-29 11:35:34

2014-08-20 16:37:51

2012-10-25 17:56:43

陈皓云计算架构师大会

2012-10-25 16:07:33

云计算架构师

2013-04-02 10:10:06

JavaScriptJS

2023-12-14 10:35:22

虚拟机程序
点赞
收藏

51CTO技术栈公众号