如何编写脚本对项目代码进行分析(性能篇)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/breavo_raw/article/details/76449287

PS:在文末有我写的封装好的python性能分析脚本,稍加修改即可直接使用 :)

在开始之前,先看看这样一句话:

我们应该忽略一些微小的效率提升,几乎在 97% 的情况下,都是如此:过早的优化是万恶之源。—— Donald Knuth

大部分时间,我们对性能的要求也许并没有想象中那么苛刻,这种想法是正确的。毕竟能够让程序正确的运行起来,就是最好的优化了。

在真正的开发过程中,我们常常会因为想要完成某个功能而不假思索的就写出了O(N^2)的代码。事后我们可能就把这件事忘到九霄云外了。你写的这段代码就像一颗定时炸弹,指不定在以后的某个时刻,突然爆炸,让你焦头烂额。

幸运的是,既然存在这样的问题,那么总是有方法可以解决的。假设你已经无法忍受项目某部分的功能了,不妨编写一个测试,看看问题究竟出现在哪里。

简单的分析

首先,从最简单的开始,编写一个计时器,了解一下你的程序整体所花费的时间。
把他做成装饰器,放在你想要测试的程序入口前面即可:

import time
from functools import wraps

def timethis(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        r = func(*args, **kwargs)
        end = time.perf_counter()
        print('{}.{} : {}'.format(func.__module__, func.__name__, end - start))
        return r
    return wrapper

但这通常无法获得足够的信息,如果想要更近一步,那么Python标准库中也提供了三种用来分析程序性能的模块,分别是cProfile, profilehotshot,另外还有一个辅助模块stats。这些模块提供了对Python程序的确定性分析功能,同时也提供了相应的报表生成工具,方便用户快速地检查和分析结果。

使用分析工具

这三个性能分析模块的介绍如下:
cProfile:基于lsprof的用C语言实现的扩展应用,运行开销比较合理,适合分析运行时间较长的程序,推荐使用这个模块;
profile:纯Python实现的性能分析模块,接口和cProfile一致。但在分析程序时增加了很大的运行开销。不过,如果你想扩展profiler的功能,可以通过继承这个模块实现;
hotshot:一个试验性的C模块,减少了性能分析时的运行开销,但是需要更长的数据后处理的次数。目前这个模块不再被维护,有可能在新版本中被弃用。

我个人比较喜欢使用的是CProfile,打印的报告十分详细,并且支持命令行或者编写脚本运行。

假设我们有这样的一个文件someprogram.py,里面有一些函数,那么,应该怎么使用cProfile去分析这个函数的性能呢?

打开cmd命令行,运行:

python3 -m cProfile your\file\path\someprogram.py

就可以获得这个文件里程序各个细节的详细报告:

859647 function calls in 16.016 CPU seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
   263169    0.080    0.000    0.080    0.000 someprogram.py:16(func)
      513    0.001    0.000    0.002    0.000 someprogram.py:30(generate_mandel)
   262656    0.194    0.000   15.295    0.000 someprogram.py:32(<genexpr>)
        1    0.036    0.036   16.077   16.077 someprogram.py:4(<module>)
   262144   15.021    0.000   15.021    0.000 someprogram.py:4(in_mandelbrot)
        1    0.000    0.000    0.000    0.000 os.py:746(urandom)
        1    0.000    0.000    0.000    0.000 png.py:1056(_readable)
        1    0.000    0.000    0.000    0.000 png.py:1073(Reader)
        1    0.227    0.227    0.438    0.438 png.py:163(<module>)
      512    0.010    0.000    0.010    0.000 png.py:200(group)
    ...

其中,输出每列的具体解释如下:
ncalls:表示函数调用的次数;
tottime:表示指定函数的总的运行时间,除掉函数中调用子函数的运行时间;
percall:(第一个percall)等于 tottime/ncalls;
cumtime:表示该函数及其所有子函数的调用运行的时间,即函数开始调用到返回的时间;
percall:(第二个percall)即函数运行一次的平均时间,等于 cumtime/ncalls;
filename:lineno(function):每个函数调用的具体信息;
另外,上面分析的时候,排序方式使用的是函数调用时间(cumulative),除了这个还有一些其他允许的排序方式:calls, cumulative, file, line, module, name, nfl, pcalls, stdname, time等。基本都是根据报告中所给出的参数进行排序设置,具体效果怎么样,大家可以尝试一下。

不过这份报告是直接打印在控制台上的,cProfile提供了额外的参数使我们可以更加灵活的对结果进行采集分析:

把分析结果保存到文件中

python -m cProfile -o result.out specify\a\file\path\someprogram.py

增加排序方式

python -m cProfile -o result.out -s cumulative specify\a\file\path\someprogram.py

你也可以在Python脚本中实现,使用python脚本的主函数代码如下:

# someprogram.py

def func():
    pass

if __name__ == "__main__":
    import cProfile
    # 直接把分析结果打印到控制台
    cProfile.run("func()")
    # 把分析结果保存到文件中
    cProfile.run("func()", filename="result.out")
    # 增加排序方式
    cProfile.run("func()", filename="result.out", sort="cumulative")

分析工具pstats

使用cProfile分析的结果可以输出到指定的文件中,但是文件内容是以二进制的方式保存的,用文本编辑器打开时乱码。所以,Python提供了一个pstats模块,用来分析cProfile输出的文件内容。它支持多种形式的报表输出,是文本界面下一个较为实用的工具。使用非常简单:

import pstats

# 创建Stats对象,result.out是你通过cProfile分析得出的结果
p = pstats.Stats("your\file\path\result.out")

# strip_dirs(): 去掉无关的路径信息
# sort_stats(): 排序,支持的方式和上述的一致
# print_stats(): 打印分析结果,可以指定打印前几行

# 和直接运行cProfile.run("func()")的结果是一样的
p.strip_dirs().sort_stats(-1).print_stats()

# 按照函数名排序,只打印前3行函数的信息
# 参数还可为小数,表示前百分之几的函数信息 
p.strip_dirs().sort_stats("name").print_stats(3)

# 按照运行时间和函数名进行排序
p.strip_dirs().sort_stats("cumulative", "name").print_stats(0.5)

# 如果想知道有哪些函数调用了sum_
p.print_callers(0.5, "sum_")

# 查看func()函数中调用了哪些函数
p.print_callees("func")

这样,我们就可以自由查看分析报告了。

使用图形化工具分析函数性能

对于一些大型的项目,我们可以使用一些图形库将分析结果视觉化,这样我们可以更加直观的观察到项目函数的运行轨迹。常见的可视化工具有Gprof2Dot,visualpytune,KCacheGrind

我个人使用的是使用 Gprof2Dot(python第三方库:使用pip install安装)和 Graphviz(for windows)
Graphviz安装完毕后可能需要手动将dot程序添加到环境变量(在bin目录下)。

linux下安装Graphviz使用命令:

sudo apt-get install graphviz

做完这些之后,(windows下)找到 Gprof2Dot脚本库所在路径,以及分析结果result.out的路径,运行:

python your\file\path\gprof2dot.py -f pstats your\file\path\result.out | dot -Tpng -o your\file\path\result.png

上面3个路径需要你自己找到或者指定,否则会出现报错。
所得到的结果如下(懒得自己动手了,网上截了个图):
这里写图片描述

最后,附上我自己写的脚本,脚本中已经将上述整个过程封装完成一个Decorator(建议将gprof2dot.py库和脚本放到一起),放在待测函数入口处即可:
https://github.com/breavo/some_useful_python_script/tree/master/use_cProfile

猜你喜欢

转载自blog.csdn.net/breavo_raw/article/details/76449287
今日推荐