当我们需要对 python 程序进行优化时,第一步要做的并不是盲目去优化,而是首先要对我们现有的程序进行分析,发现程序的性能瓶颈然后进行针对性的优化,这里采用 Python 中常用的性能分析器 cProfiler,并使用 Gprof2Dot 将分析器输出转换成 Graphviz 可处理的图像表述,配合 dot 命令,即可得到不同函数所消耗的时间分析图。
第一步
我们需要写一个带有参数的性能分析装饰器,其中主要用到 cProfile 模块的 Profile 类和 pstat 模块的 Stats 类。
Profile 类:
enable(): 开始收集性能分析数据
disable(): 停止收集性能分析数据
create_stats(): 停止收集分析数据,并为已收集的数据创建 stats 对象
print_stats(): 创建 stats 对象并打印分析结果
dump_stats(filename): 把当前性能分析的结果写入文件 (二进制格式)
runcall(func, *args, **kwargs): 收集被调用函数 func 的性能分析数据
Stats 类:
下面直接上代码
import cProfile
import pstats
import os
# 性能分析装饰器定义
def do_cprofile(filename):
def wrapper(func):
def profiled_func(*args, **kwargs):
# Flag for do profiling or not.
DO_PROF = os.getenv("PROFILING")
if DO_PROF:
profile = cProfile.Profile()
profile.enable()
result = func(*args, **kwargs)
profile.disable()
# Sort stat by internal time.
sortby = "tottime"
ps = pstats.Stats(profile).sort_stats(sortby)
ps.dump_stats(filename)
else:
result = func(*args, **kwargs)
return result
return profiled_func
return wrapper
这样我们就可以在我们想进行分析的地方进行性能分析,例如我想分析 supplier.py 中 class SupplierViewSet(BaseViewSet):类中的 get_queryset() 方法
class SupplierViewSet(BaseViewSet):
# ...
# 应用装饰器来分析函数
@do_cprofile("./cpf_run.prof")
def get_queryset(self):
# ...
装饰器函数中通过 sys.getenv 来获取环境变量判断是否需要进行分析,因此可以通过设置环境变量来告诉程序是否进行性能分析:
export PROFILING=y
程序跑完后便会在当前路径下生成 cpf_run.prof 的分析文件,此时我们需要将该文件从远程服务器取到本地,然后我们就可以通过可视化工具对函数进行分析。
下载 gprof2dot.py 项目地址:https://github.com/jrfonseca/gprof2dot
下载 Graphviz 工具
将 gprof2dot.py python 文件放入 Graphviz 的 bin 目录下
将生成的 cpf_run.prof 文件放入 Graphviz 的 bin 目录下
然后在 bin 路径下执行以下命令
python gprof2dot.py -f pstats cpf_run.prof | dot -Tpng -o cpf_run.png
生成 cpf_run.png 如下:
根据可视化图片进行分析,顺着浅色方格的看下去很容易发现程序的瓶颈部分
每个 node 的信息如下:
+------------------------------+|
| function name |
| total time % ( self time % ) |
| total calls |
+------------------------------+
每个 edge 的信息如下:
total time %
calls
parent --------------------> children
具体含义可以在https://github.com/jrfonseca/gprof2dot中看到,这里只做部分解释。