AIOps 一场颠覆传统运维的盛筵
646
2022-09-19
Python 应用剖析工具介绍
工具
闲话少叙,下面开始介绍分析 Python 代码的几种便捷工具。
cProfile
CPython distribution 自带两种分析工具:profile 与 cProfile。两者使用同样的 API,按理说运行效果应该差不多。然而,前者的运行时开销更大,因此,本文将主要介绍 cProfile。
借助 cProfile,可以轻松实现对代码的深入分析,并且了解代码的哪些部分亟待提升。查看下面的缓慢代码实例:
--> % cat slow.pyimport timedef main(): sum = 0 for i in range(10): sum += expensive(i // 2) return sum def expensive(t): time.sleep(t) return t if __name__ == '__main__': print(main())
在上面的代码中,笔者通过调用 time.sleep 方法,模拟一个运行时间很长的程序,并假定运行结果很重要。接下来,对这段代码进行分析,结果如下:
--> % python -m cProfile slow.py20 34 function calls in 20.030 seconds Ordered by: standard name ncalls tottime percall cumtime percall filename:lineno(function) 1 0.000 0.000 0.000 0.000 __future__.py:48(
我们发现,分析结果相当琐碎。其实,可以用更有益的方式组织分析结果。在上例中,调用列表是按照字母顺序排列的,这对我们并无价值。笔者更愿意看到按照调用次数或累计运行时间排列的调用情况。幸运的是,通过 -s 参数就能实现这一点。我们马上就能看到存在问题的代码段了!
--> % python -m cProfile -s calls slow.py20 34 function calls in 20.028 seconds Ordered by: call count ncalls tottime percall cumtime percall filename:lineno(function) 10 0.000 0.000 20.025 2.003 slow.py:11(expensive) 10 20.025 2.003 20.025 2.003 {time.sleep} 7 0.000 0.000 0.000 0.000 __future__.py:75(__init__) 1 0.000 0.000 20.026 20.026 slow.py:5(main) 1 0.000 0.000 0.000 0.000 __future__.py:74(_Feature) 1 0.000 0.000 0.000 0.000 {print} 1 0.000 0.000 0.000 0.000 __future__.py:48(
果然!我们发现,存在问题的代码就在 expensive 函数当中。该函数在执行结束之前调用了多次 time.sleep 方法,因此导致了程序的速度下降。
基本功能介绍完毕之后,让我们来看看使用分析工具查找问题代码的其他方法。
PyCallGraph
-> % pip install pycallgraph
通过下面的指令,就能运行图形化应用:
-> % pycallgraph graphviz -- python slow.py
运行完毕之后,在运行脚本的目录下会出现一张 pycallgraph.png 图片文件。同时,还应该得到相似的分析结果(如果你之前已经用 cProfile 分析过了)。结果中的数据应该与 cProfile 提供的结果一致。不过,PyCallGraph 的优点在于,它能展示被调用函数相互间的关系。
让我们来看看图片到底长什么样:
这多方便啊!图片显示了程序的运行路径,告诉我们程序经历过的每个函数、模块以及文件,还带有运行时间与调用次数等信息。如果在庞大的应用中运行该分析工具,会得到一张巨大的图片。但是,根据颜色的差别,我们仍能轻易找到存在问题的代码块。下面是 PyCallGraph 文档中提供的一张图片,展示了一段复杂的正则表达式调用中代码的运行路径:
这些信息有什么用?
一旦我们确定了导致问题代码的根源,就可以选择合适的解决方案优化代码,为其提速。下面,让我们根据特定的情况,探讨一些缓慢代码可行的解决方案。
I/O
正则表达式
人们都说,一旦你决定用正则表达式解决某个问题,你就有两个问题要解决了。正则表达式真的很难用对,而且难以维护。关于这一点,笔者可以写一篇长篇大论进行阐述。(但是,我不会写的:)。正则表达式真的不简单,我相信有很多博文已经做了详尽的阐述。)不过,在此,笔者将介绍几个有用的技巧:
以上是有关正则表达式笔者想说的全部内容。如果你想要更多信息,相信网络上还有很多好的文章。
Python 代码
from functools import wrapsdef memoize(f): cache = {} @wraps(f) def inner(arg): if arg not in cache: cache[arg] = f(arg) return cache[arg] return inner
import time@memoizedef slow(you): time.sleep(3) print("Hello after 3 seconds, {}!".format(you)) return 3
现在,如果我们多次运行该函数,运行结果就会立即出现:
>>> slow("Davis")Hello after 3 seconds, Davis!3>>> slow("Davis")3>>> slow("Visitor")Hello after 3 seconds, Visitor!3>>> slow("Visitor")3
对于该项目来说,这是极大的速度提升。而且代码运行起来也没有出现故障。
其他情况
如果你的代码无法使用记忆(memoization)技巧,你的算法也不像 O(n!) 这样疯狂,或者代码的剖析结果也没有引人注意的地方,这可能说明你的代码并不存在显著的问题。这时候,你可以尝试一下别的运行环境或语言。PyPy 就是一个好的选择,你可能还要将算法用C语言扩展方法重写一下。幸运的是,笔者之前的项目并未走到这一步,但是这仍是很好的排错方案。
结论
剖析代码可以帮助你理解项目的执行流程、找出潜在的问题代码,以及作为开发者该如何提升程序运行速度。Python 剖析工具不但功能强大,简单易用,而且足够深入以快速找出问题根源。虽然 Python 并不是以快速著称的语言,但这并不意味着你的代码应该拖拖拉拉。管理好自己的算法,适时进行剖析,但绝不要过早优化!
发表评论
暂时没有评论,来抢沙发吧~