今天着手对战斗力程序的性能做了一个分析和优化,利用下面将要介绍的工具,一步一步的调优,减少了很多性能上的黑洞。

cProfile

使用工具cProfile,在一个脚本中写好要测试的程序,然后在终端中运行python -m cProfile -s tottime calc_efficiency.py表示对单个函数的运行总时间进行统计,这样就会在终端上打印出来性能分析结果,比如这样:

1
2
3
4
5
6
7
8
9
10
1723 function calls (1578 primitive calls) in 0.004 seconds
Ordered by: internal time
ncalls tottime percall cumtime percall filename:lineno(function)
4 0.000 0.000 0.000 0.000 _methods.py:86(_var)
13 0.000 0.000 0.000 0.000 calculators.py:18(nan2zero)
2 0.000 0.000 0.000 0.000 financial.py:25(_adjust_returns)
2 0.000 0.000 0.000 0.000 {method 'cumprod' of 'numpy.ndarray' objects}
2 0.000 0.000 0.000 0.000 {method 'accumulate' of 'numpy.ufunc' objects}

表示1723个函数调用被监控,其中1578是原生调用,即不涉及递归调用,总共执行时间是0.004秒,结果列表是按照函数内部调用时间来排序,即不包括他自己调用的其他函数的时间。

  • ncalls 表示调用次数,如果是4/3 表示4次总调用次数,3表示原生调用次数
  • tottime 表示函数内部调用时间
  • percall 是tottime/ncalls
  • cumtime 累计时间,包含了自己内部调用函数的时间
  • 最后一列,文件名,行号,函数名

cProfile除了可以在终端上使用,还有编程接口cProfile.run('re.compile("foo|bar")')

具体cProfile的使用方法请戳这里

另外官方给出的使用pstats模块的Stats类来读取和操作stats文件,Analysis of the profiler data is done using the Stats class.

一个使用示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import cProfile
import pstats
from ability import WholeAbility
import time
profile = cProfile.Profile()
profile.enable()
t0 = time.time()
fundId = "test"
start = "20151021"
end = "20151023"
wa = WholeAbility(fundId, start, end, assets, flows, stocks)
ability_data, positions, trades = wa.calc_ability()
summary_data = wa.summary()
print("using {}".format(time.time()-t0))
profile.disable()
profile.print_stats(sort='tottime')
sortby = "tottime"
ps = pstats.Stats(profile).sort_stats(sortby)
ps.dump_stats("./eff.txt")

使用dump_stats可以把当前性能分析结果写入文件,这个结果可以在后面进行可视化分析。

gprof2dot 分析结果可视化

安装

  • Mac: brew install graphviz
  • Ubuntu: apt-get install python graphviz

下载

pip install gprof2dot

使用

然后就可以使用gprof2dot来调取之前存储的分析结果来生成可视化数据了,相当好用,利用这张图,就可以看出你的程序哪里是性能瓶颈了,然后有目的的去优化,再分析!

gprof2dot -f pstats mkm_run.prof | dot -Tpng -o mkm_run.png

其中每个node的信息如下:

1
2
3
4
5
+------------------------------+
| function name |
| total time % ( self time % ) |
| total calls |
+------------------------------+

每个edge的信息如下:

1
2
3
total time %
calls
parent --------------------> children

备注

  1. 尽量不要使用deepcopy,很慢,详见这篇文章蜗牛般的 Python 深拷贝
  2. 优化算法,不要在循环里面进行耗时操作