Python性能分析指南
http://www.admin10000.com/document/2861.html
尽管并非每个你写的Python程序都需要严格的性能分析,但了解一下Python的生态系统中很多优秀的在你需要做性能分析的时候可以使用的工具仍然是一件值得去做的事。
分析一个程序的性能,最终都归结为回答4个基本的问题:
- 程序运行速度有多快?
 - 运行速度瓶颈在哪儿?
 - 程序使用了多少内存?
 - 内存泄露发生在哪里?
 
下面,我们将使用一些优秀的工具深入回答这些问题。
使用time工具粗糙定时
首先,我们可以使用快速然而粗糙的工具:古老的unix工具time,来为我们的代码检测运行时间。
| 
 1 
2 
3 
4 
5 
 | 
$ time python yourprogram.pyreal    0m1.028suser    0m0.001ssys     0m0.003s | 
上面三个输入变量的意义在文章 stackoverflow article 中有详细介绍。简单的说:
- real - 表示实际的程序运行时间
 - user - 表示程序在用户态的cpu总时间
 - sys - 表示在内核态的cpu总时间
 
通过sys和user时间的求和,你可以直观的得到系统上没有其他程序运行时你的程序运行所需要的CPU周期。
若sys和user时间之和远远少于real时间,那么你可以猜测你的程序的主要性能问题很可能与IO等待相关。
使用计时上下文管理器进行细粒度计时
我们的下一个技术涉及访问细粒度计时信息的直接代码指令。这是一小段代码,我发现使用专门的计时测量是非常重要的:
timer.py
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
 | 
import timeclass Timer(object):    def __init__(self, verbose=False):        self.verbose = verbose    def __enter__(self):        self.start = time.time()        return self    def __exit__(self, *args):        self.end = time.time()        self.secs = self.end - self.start        self.msecs = self.secs * 1000  # millisecs        if self.verbose:            print 'elapsed time: %f ms' % self.msecs | 
为了使用它,你需要用Python的with关键字和Timer上下文管理器包装想要计时的代码块。它将会在你的代码块开始执行的时候启动计时器,在你的代码块结束的时候停止计时器。
这是一个使用上述代码片段的例子:
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
 | 
from timer import Timerfrom redis import Redisrdb = Redis()with Timer() as t:    rdb.lpush("foo", "bar")print "=> elasped lpush: %s s" % t.secswith Timer as t:    rdb.lpop("foo")print "=> elasped lpop: %s s" % t.secs | 
我经常将这些计时器的输出记录到文件中,这样就可以观察我的程序的性能如何随着时间进化。
使用分析器逐行统计时间和执行频率
Robert Kern有一个称作line_profiler的不错的项目,我经常使用它查看我的脚步中每行代码多快多频繁的被执行。
想要使用它,你需要通过pip安装该python包:
| 
 1 
 | 
$ pip install line_profiler | 
一旦安装完成,你将会使用一个称做“line_profiler”的新模组和一个“kernprof.py”可执行脚本。
想要使用该工具,首先修改你的源代码,在想要测量的函数上装饰@profile装饰器。不要担心,你不需要导入任何模组。kernprof.py脚本将会在执行的时候将它自动地注入到你的脚步的运行时。
primes.py
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
 | 
@profiledef primes(n):     if n==2:        return [2]    elif n<2:        return []    s=range(3,n+1,2)    mroot = n ** 0.5    half=(n+1)/2-1    i=0    m=3    while m <= mroot:        if s[i]:            j=(m*m-3)/2            s[j]=0            while j<half:                s[j]=0                j+=m        i=i+1        m=2*i+3    return [2]+[x for x in s if x]primes(100) | 
一旦你已经设置好了@profile装饰器,使用kernprof.py执行你的脚步。
| 
 1 
 | 
$ kernprof.py -l -v fib.py | 
-l选项通知kernprof注入@profile装饰器到你的脚步的内建函数,-v选项通知kernprof在脚本执行完毕的时候显示计时信息。上述脚本的输出看起来像这样:
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
26 
27 
28 
29 
30 
 | 
Wrote profile results to primes.py.lprofTimer unit: 1e-06 sFile: primes.pyFunction: primes at line 2Total time: 0.00019 sLine #      Hits         Time  Per Hit   % Time  Line Contents==============================================================     2                                           @profile     3                                           def primes(n):      4         1            2      2.0      1.1      if n==2:     5                                                   return [2]     6         1            1      1.0      0.5      elif n<2:     7                                                   return []     8         1            4      4.0      2.1      s=range(3,n+1,2)     9         1           10     10.0      5.3      mroot = n ** 0.5    10         1            2      2.0      1.1      half=(n+1)/2-1    11         1            1      1.0      0.5      i=0    12         1            1      1.0      0.5      m=3    13         5            7      1.4      3.7      while m <= mroot:    14         4            4      1.0      2.1          if s[i]:    15         3            4      1.3      2.1              j=(m*m-3)/2    16         3            4      1.3      2.1              s[j]=0    17        31           31      1.0     16.3              while j<half:    18        28           28      1.0     14.7                  s[j]=0    19        28           29      1.0     15.3                  j+=m    20         4            4      1.0      2.1          i=i+1    21         4            4      1.0      2.1          m=2*i+3    22        50           54      1.1     28.4      return [2]+[x for x in s if x] | 
寻找具有高Hits值或高Time值的行。这些就是可以通过优化带来最大改善的地方。
程序使用了多少内存?
现在我们对计时有了较好的理解,那么让我们继续弄清楚程序使用了多少内存。我们很幸运,Fabian Pedregosa模仿Robert Kern的line_profiler实现了一个不错的内存分析器。
首先使用pip安装:
| 
 1 
2 
 | 
$ pip install -U memory_profiler$ pip install psutil | 
(这里建议安装psutil包,因为它可以大大改善memory_profiler的性能)。
就像line_profiler,memory_profiler也需要在感兴趣的函数上面装饰@profile装饰器:
| 
 1 
2 
3 
4 
 | 
@profiledef primes(n):     ...    ... | 
想要观察你的函数使用了多少内存,像下面这样执行:
| 
 1 
 | 
$ python -m memory_profiler primes.py | 
一旦程序退出,你将会看到看起来像这样的输出:
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
 | 
Filename: primes.pyLine #    Mem usage  Increment   Line Contents==============================================     2                           @profile     3    7.9219 MB  0.0000 MB   def primes(n):      4    7.9219 MB  0.0000 MB       if n==2:     5                                   return [2]     6    7.9219 MB  0.0000 MB       elif n<2:     7                                   return []     8    7.9219 MB  0.0000 MB       s=range(3,n+1,2)     9    7.9258 MB  0.0039 MB       mroot = n ** 0.5    10    7.9258 MB  0.0000 MB       half=(n+1)/2-1    11    7.9258 MB  0.0000 MB       i=0    12    7.9258 MB  0.0000 MB       m=3    13    7.9297 MB  0.0039 MB       while m <= mroot:    14    7.9297 MB  0.0000 MB           if s[i]:    15    7.9297 MB  0.0000 MB               j=(m*m-3)/2    16    7.9258 MB -0.0039 MB               s[j]=0    17    7.9297 MB  0.0039 MB               while j<half:    18    7.9297 MB  0.0000 MB                   s[j]=0    19    7.9297 MB  0.0000 MB                   j+=m    20    7.9297 MB  0.0000 MB           i=i+1    21    7.9297 MB  0.0000 MB           m=2*i+3    22    7.9297 MB  0.0000 MB       return [2]+[x for x in s if x] | 
line_profiler和memory_profiler的IPython快捷方式
memory_profiler和line_profiler有一个鲜为人知的小窍门,两者都有在IPython中的快捷命令。你需要做的就是在IPython会话中输入以下内容:
| 
 1 
2 
 | 
%load_ext memory_profiler%load_ext line_profiler | 
在这样做的时候你需要访问魔法命令%lprun和%mprun,它们的行为类似于他们的命令行形式。主要区别是你不需要使用@profiledecorator来修饰你要分析的函数。只需要在IPython会话中像先前一样直接运行分析:
| 
 1 
2 
3 
 | 
In [1]: from primes import primesIn [2]: %mprun -f primes primes(1000)In [3]: %lprun -f primes primes(1000) | 
这样可以节省你很多时间和精力,因为你的源代码不需要为使用这些分析命令而进行修改。
内存泄漏在哪里?
cPython解释器使用引用计数做为记录内存使用的主要方法。这意味着每个对象包含一个计数器,当某处对该对象的引用被存储时计数器增加,当引用被删除时计数器递减。当计数器到达零时,cPython解释器就知道该对象不再被使用,所以删除对象,释放占用的内存。
如果程序中不再被使用的对象的引用一直被占有,那么就经常发生内存泄漏。
查找这种“内存泄漏”最快的方式是使用Marius Gedminas编写的objgraph,这是一个极好的工具。该工具允许你查看内存中对象的数量,定位含有该对象的引用的所有代码的位置。
一开始,首先安装objgraph:
| 
 1 
 | 
pip install objgraph | 
一旦你已经安装了这个工具,在你的代码中插入一行声明调用调试器:
| 
 1 
 | 
import pdb; pdb.set_trace() | 
最普遍的对象是哪些?
在运行的时候,你可以通过执行下述指令查看程序中前20个最普遍的对象:
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
 | 
(pdb) import objgraph(pdb) objgraph.show_most_common_types()MyBigFatObject             20000tuple                      16938function                   4310dict                       2790wrapper_descriptor         1181builtin_function_or_method 934weakref                    764list                       634method_descriptor          507getset_descriptor          451type                       439 | 
哪些对象已经被添加或删除?
我们也可以查看两个时间点之间那些对象已经被添加或删除:
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
 | 
(pdb) import objgraph(pdb) objgraph.show_growth()...(pdb) objgraph.show_growth()   # this only shows objects that has been added or deleted since last show_growth() calltraceback                4        +2KeyboardInterrupt        1        +1frame                   24        +1list                   667        +1tuple                16969        +1 | 
谁引用着泄漏的对象?
继续,你还可以查看哪里包含给定对象的引用。让我们以下述简单的程序做为一个例子:
| 
 1 
2 
3 
 | 
x = [1]y = [x, [x], {"a":x}]import pdb; pdb.set_trace() | 
想要看看哪里包含变量x的引用,执行objgraph.show_backref()函数:
| 
 1 
2 
 | 
(pdb) import objgraph(pdb) objgraph.show_backref([x], filename="/tmp/backrefs.png") | 
该命令的输出应该是一副PNG图像,保存在/tmp/backrefs.png,它看起来是像这样:
最下面有红字的盒子是我们感兴趣的对象。我们可以看到,它被符号x引用了一次,被列表y引用了三次。如果是x引起了一个内存泄漏,我们可以使用这个方法,通过跟踪它的所有引用,来检查为什么它没有自动的被释放。
回顾一下,objgraph 使我们可以:
- 显示占据python程序内存的头N个对象
 - 显示一段时间以后哪些对象被删除活增加了
 - 在我们的脚本中显示某个给定对象的所有引用
 
努力与精度
在本帖中,我给你显示了怎样用几个工具来分析python程序的性能。通过这些工具与技术的武装,你可以获得所有需要的信息,来跟踪一个python程序中大多数的内存泄漏,以及识别出其速度瓶颈。
对许多其他观点来说,运行一次性能分析就意味着在努力目标与事实精度之间做出平衡。如果感到困惑,那么就实现能适应你目前需求的最简单的解决方案。
Python性能分析指南的更多相关文章
- Python性能分析指南(未完成)
		
英文原文:http://www.huyng.com/posts/python-performance-analysis/ 译文:http://www.oschina.net/translate/pyt ...
 - Python性能分析指南 - 技术翻译 - 开源中国社区
		
http://www.oschina.net/translate/python-performance-analysis
 - Python性能分析
		
Python性能分析 https://www.cnblogs.com/lrysjtu/p/5651816.html https://www.cnblogs.com/cbscan/articles/33 ...
 - python性能分析(一)——使用timeit给你的程序打个表吧
		
前言 我们可以通过查看程序核心算法的代码,得知核心算法的渐进上界或者下界,从而大概估计出程序在运行时的效率,但是这并不够直观,也不一定十分靠谱(在整体程序中仍有一些不可忽略的运行细节在估计时被忽略了) ...
 - 如何进行 Python性能分析,你才能如鱼得水?
		
[编者按]本文作者为 Bryan Helmig,主要介绍 Python 应用性能分析的三种进阶方案.文章系国内 ITOM 管理平台 OneAPM 编译呈现. 我们应该忽略一些微小的效率提升,几乎在 9 ...
 - Python性能分析工具Profile
		
Python性能分析工具Profile 代码优化的前提是需要了解性能瓶颈在什么地方,程序运行的主要时间是消耗在哪里,对于比较复杂的代码可以借助一些工具来定位,python 内置了丰富的性能分析工具,如 ...
 - Python性能分析与优化PDF高清完整版免费下载|百度云盘
		
百度云盘|Python性能分析与优化PDF高清完整版免费下载 提取码:ubjt 内容简介 全面掌握Python代码性能分析和优化方法,消除性能瓶颈,迅速改善程序性能! 对于Python程序员来说,仅仅 ...
 - Python—— 性能分析入门指南
		
虽然并非你编写的每个 Python 程序都要求一个严格的性能分析,但是让人放心的是,当问题发生的时候,Python 生态圈有各种各样的工具可以处理这类问题. 分析程序的性能可以归结为回答四个基本问题: ...
 - Python程序的性能分析指南(转)
		
原文地址 :http://blog.jobbole.com/47619/ 虽然不是所有的Python程序都需要严格的性能分析,不过知道如何利用Python生态圈里的工具来分析性能,也是不错的. 分析一 ...
 
随机推荐
- Codevs  1003  电话连线
			
时间限制: 1 s 空间限制: 128000 K 题目等级 : 黄金 Gold 题目描述 Description 一个国家有n个城市.若干个城市之间有电话线连接,现在要增加m条电话线(电话线当 ...
 - daily news新闻阅读客户端应用源码(兼容iPhone和iPad)
			
daily news新闻阅读客户端应用源码(兼容iPhone和iPad),也是一款兼容性较好的应用,可以支iphone和ipad的阅读阅读器源码,设计风格和排列效果很不错,现在做新闻资讯客户端的朋友可 ...
 - JavaScript 内部对象
			
欢迎访问我的个人博客:http://blog.cdswyda.com/ 原文文地址:JavaScript 内部对象
 - hi,mongo!(1)
			
用了很多年的关系型数据库,想换一种思路,学习一下最近比较火的mongo数据库. 一.下载.安装mongo 下载地址:http://www.mongodb.org/downloads(官网) 官网的mo ...
 - ios特性访问器方法(setter和getter)
			
Employee.h @interface Employee:NSObject { int _employeeNumber; NSString *_name; Employee*_supervisit ...
 - openerp经典收藏 深入理解报表运行机制(转载)
			
深入理解报表运行机制 原文:http://blog.sina.com.cn/s/blog_57ded94e01014ppd.html 1) OpenERP报表的基本运行机制 OpenERP报表的 ...
 - C# 枚举,传入int值返回string值
			
需求:1:子公司负责人2:人事3:审批人4:签批人 5:管理员 传入值为1,2,3,4,5这个数字的某一个.需要返回他们的中文描述. 一下忘记该怎么写了...后来百度下查出来了..记录下当个小工具吧 ...
 - Storm集群安装详解
			
storm有两种操作模式: 本地模式和远程模式. 本地模式:你可以在你的本地机器上开发测试你的topology, 一切都在你的本地机器上模拟出来; 远端模式:你提交的topology会在一个集群的机器 ...
 - Oracle 主键
			
给student 表产生 自增的序列主键 increment ; ----IBATIS简单入门教程http://www.cnblogs.com/ycxyyzw/archive/2012/10/13/2 ...
 - python之函数嵌套
			
python很多特性与JavaScript是相似甚至相同的: 1. 无类型 2. 函数亦对象 .... 自然: python也允许函数嵌套, 这与JavaScript中函数闭包的作用一样....