程序使用了多少内存?

现在我们对计时有了较好的理解,那么让我们继续弄清楚程序使用了多少内存。我们很幸运,Fabian Pedregosa模仿Robert Kern的line_profiler实现了一个不错的内存分析器。

首先使用pip安装::

  1. $ pip install -U memory_profiler 
  2. $ pip install psutil 

这里建议安装psutil包,因为它可以大大改善memory_profiler的性能)。

就像line_profiler,memory_profiler也需要在感兴趣的函数上面装饰@profile装饰器:

  1. @profile 
  2. def primes(n):  
  3.     ... 
  4.     ... 

想要观察你的函数使用了多少内存,像下面这样执行:

  1. $ python -m memory_profiler primes.py 

一旦程序退出,你将会看到看起来像这样的输出:

  1. Filename: primes.py 
  2.  
  3. Line #    Mem usage  Increment   Line Contents 
  4. ============================================== 
  5.      2                           @profile 
  6.      3    7.9219 MB  0.0000 MB   def primes(n):  
  7.      4    7.9219 MB  0.0000 MB       if n==2
  8.      5                                   return [2
  9.      6    7.9219 MB  0.0000 MB       elif n<2
  10.      7                                   return [] 
  11.      8    7.9219 MB  0.0000 MB       s=range(3,n+1,2
  12.      9    7.9258 MB  0.0039 MB       mroot = n ** 0.5 
  13.     10    7.9258 MB  0.0000 MB       half=(n+1)/2-1 
  14.     11    7.9258 MB  0.0000 MB       i=0 
  15.     12    7.9258 MB  0.0000 MB       m=3 
  16.     13    7.9297 MB  0.0039 MB       while m <= mroot: 
  17.     14    7.9297 MB  0.0000 MB           if s[i]: 
  18.     15    7.9297 MB  0.0000 MB               j=(m*m-3)/2 
  19.     16    7.9258 MB -0.0039 MB               s[j]=0 
  20.     17    7.9297 MB  0.0039 MB               while j<half: 
  21.     18    7.9297 MB  0.0000 MB                   s[j]=0 
  22.     19    7.9297 MB  0.0000 MB                   j+=m 
  23.     20    7.9297 MB  0.0000 MB           i=i+1 
  24.     21    7.9297 MB  0.0000 MB           m=2*i+3 
  25.     22    7.9297 MB  0.0000 MB       return [2]+[x for x in s if x] 

ine_profiler和memory_profiler的IPython快捷方式

memory_profiler和line_profiler有一个鲜为人知的小窍门,两者都有在IPython中的快捷命令。你需要做的就是在IPython会话中输入以下内容:

  1. %load_ext memory_profiler 
  2. %load_ext line_profiler 

在这样做的时候你需要访问魔法命令%lprun和%mprun,它们的行为类似于他们的命令行形式。主要区别是你不需要使用@profiledecorator来修饰你要分析的函数。只需要在IPython会话中像先前一样直接运行分析:

  1. In [1]: from primes import primes 
  2. In [2]: %mprun -f primes primes(1000
  3. 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. (pdb) import objgraph 
  2. (pdb) objgraph.show_most_common_types() 
  3.  
  4. MyBigFatObject             20000 
  5. tuple                      16938 
  6. function                   4310 
  7. dict                       2790 
  8. wrapper_descriptor         1181 
  9. builtin_function_or_method 934 
  10. weakref                    764 
  11. list                       634 
  12. method_descriptor          507 
  13. getset_descriptor          451 
  14. type                       439 

哪些对象已经被添加或删除?

我们也可以查看两个时间点之间那些对象已经被添加或删除:

  1. (pdb) import objgraph 
  2. (pdb) objgraph.show_growth() 
  3. (pdb) objgraph.show_growth()   # this only shows objects that has been added or deleted since last show_growth() call 
  4.  
  5. traceback                4        +2 
  6. KeyboardInterrupt        1        +1 
  7. frame                   24        +1 
  8. list                   667        +1 
  9. tuple                16969        +1 

谁引用着泄漏的对象?

继续,你还可以查看哪里包含给定对象的引用。让我们以下述简单的程序做为一个例子:

  1. x = [1
  2. y = [x, [x], {"a":x}] 
  3. import pdb; pdb.set_trace() 

想要看看哪里包含变量x的引用,执行objgraph.show_backref()函数:

  1. (pdb) import objgraph 
  2. (pdb) objgraph.show_backref([x], filename="/tmp/backrefs.png"

该命令的输出应该是一副PNG图像,保存在/tmp/backrefs.png,它看起来是像这样:

back refrences

最下面有红字的盒子是我们感兴趣的对象。我们可以看到,它被符号x引用了一次,被列表y引用了三次。如果是x引起了一个内存泄漏,我们可以使用这个方法,通过跟踪它的所有引用,来检查为什么它没有自动的被释放。

回顾一下,objgraph 使我们可以:

  • 显示占据python程序内存的头N个对象
  • 显示一段时间以后哪些对象被删除活增加了
  • 在我们的脚本中显示某个给定对象的所有引用

努力与精度

在本帖中,我给你显示了怎样用几个工具来分析python程序的性能。通过这些工具与技术的武装,你可以获得所有需要的信息,来跟踪一个python程序中大多数的内存泄漏,以及识别出其速度瓶颈。

对许多其他观点来说,运行一次性能分析就意味着在努力目标与事实精度之间做出平衡。如果感到困惑,那么就实现能适应你目前需求的最简单的解决方案。

译文链接:http://www.oschina.net/translate/python-performance-analysis


评论关闭