上帝说:“选择了脚本,就不要考虑性能。”我是很支持这句话的,使用脚本要的就是开发速度、良好的扩展性以及可维护性。可惜到了最后,我们的程序难免会运行得太慢,我们的客户不能忍受,这时候,我们就不得不考虑对代码的性能进行优化了。
程序运行慢的原因有很多,比如存在太多的劣化代码(如在程序中存在大量的“.”操作符),但真正的原因往往是比较是一两段设计并不那么良好的不起眼的程序,比如对一序列元素进行自定义的类型转换等。因为程序性能影响是符合80/20法则的,即20%的代码的运行时间占用了80%的总运行时间(实际上,比例要夸张的多,通常是几十行代码占用了95%以上的运行时间),靠经验就很难找出造成性能瓶颈的代码了。这时候,我们需要一个工具——profile!最近我手上的项目也在一些关键的地方遇到了性能问题,那时已经接近项目完工日期,幸好因为平时的代码模块化程度比较高,所以通过profile分析相关的独立模块,基本上解决了性能问题。通过这件事,让我下决心写一篇关于profile的文章,分享一下profile的使用心得。
profile是python的标准库。可以统计程序里每一个函数的运行时间,并且提供了多样化的报表。使用profile来分析一个程序很简单,举例说如果有一个程序如下:
def foo():
sum = 0
for i in range(100):
sum += i
return sum
if __name__ == "__main__":
foo()
|
现在要用profile分析这个程序,很简单,把if程序块改为如下:
if __name__ == "__main__":
import profile
profile.run("foo()")
|
我们仅仅是import了profile这个模块,然后以程序的入口函数名为参数调用了profile.run这个函数,程序运行的输出如下:
5 function calls in 0.143 CPU seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.000 0.000 0.000 0.000 :0(range)
1 0.143 0.143 0.143 0.143 :0(setprofile)
1 0.000 0.000 0.000 0.000 <string>:1(?)
1 0.000 0.000 0.000 0.000 prof1.py:1(foo)
1 0.000 0.000 0.143 0.143 profile:0(foo())
0 0.000 0.000 profile:0(profiler)
|
上图显示了prof1.py里函数调用的情况,根据图表我们可以清楚地看到foo()函数占用了100%的运行时间,foo()函数是这个程序里名至实归的热点。
除了用这种方式,profile还可以直接用python解释器调用profile模块来剖分py程序,如在命令行界面输入如下命令:
python -m profile prof1.py |
产生的输出跟直接修改脚本调用profile.run()函数有一样的功效。
profile的统计结果分为ncalls, tottime, percall, cumtime, percall, filename:lineno(function)等若干列:
ncalls
|
函数的被调用次数
|
tottime
|
函数总计运行时间,除去函数中调用的函数运行时间
|
percall
|
函数运行一次的平均时间,等于
tottime/ncalls
|
cumtime
|
函数总计运行时间,含调用的函数运行时间
|