博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
(转)代码执行的效率
阅读量:7167 次
发布时间:2019-06-29

本文共 3469 字,大约阅读时间需要 11 分钟。

原文地址:

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

第一个例子

PHP中Getter和Setter的效率()

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

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

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在打开优化的时候也可以优化之。但是对于动态语言来说,这个事就变得有 点困难了。

 

你可能会以为使用下面的代码(Magic Function)会好一些,但实际其性能更差。

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有更好的性能,你可能需要使用来把PHP编译成C语言。

第二个例子

为什么Python程序在函数内执行得更快?()

考虑下面的代码,一个在函数体内,一个是全局的代码。

函数内的代码执行效率为 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

不用太纠结时间,只是一个示例,我们可以看到效率查得很多。为什么会这样呢?我们使用  反汇编函数体内的bytecode 代码,使用  反汇编全局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

我们可以看到,差别就是  和 ,前者比后者快很多。所以,在全局代码中,变量i成了一个全局变量,而函数中的i是放在本地变量表中,所以在全局变量表中查找变量就慢很多。如果你在main函数中声明global i 那么效率也就下来了。原因是,本地变量是存在一个数组中(直到),用一个整型常量去访问,而全局变量存在一个dictionary中,查询很慢。

(注:在C/C++中,这个不是一个问题)

第三个例子

为什么排好序的数据在遍历时会更快?()

参看如下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,或是别的什么语言都基本上一样。

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

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

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

Image by Mecanismo, from Wikimedia Commons:

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

最后,推荐大家一个网站——,网站上的有一些教程告诉你。

(全文完)

转载地址:http://mymwm.baihongyu.com/

你可能感兴趣的文章
运算符、表达式概论
查看>>
软件架构师应该知道的97件事
查看>>
Linux 用户和组 (4) userdel
查看>>
php面向对象三,继承父类extends
查看>>
网络编程学习——带外数据
查看>>
iOS开发编码建议与编程经验
查看>>
DecimalFormat 类基本使用
查看>>
es6 Set和map数据结构
查看>>
数字键盘三
查看>>
12个值得关注的顶级JS库
查看>>
线程安全的CopyOnWriteArrayList介绍
查看>>
CAD转换为图片可以设置哪些格式
查看>>
orcl 自定义排序
查看>>
AES加密解密算法简介
查看>>
KVM虚拟机管理指南(纯命令行模式)
查看>>
Hibernate 一对多注解 mappedby 作用
查看>>
grub legacy练习 之 单用户模式修改root账户口令,并为grub菜单项设置密码保护功能...
查看>>
VLAN(单臂路由,三层路由功能)
查看>>
CentOS安装rstatd服务
查看>>
解决centos7命令行中文乱码
查看>>