专栏名称: Python开发者
人生苦短,我用 Python。伯乐在线旗下账号「Python开发者」分享 Python 相关的技术文章、工具资源、精选课程、热点资讯等。
目录
相关文章推荐
51好读  ›  专栏  ›  Python开发者

Python 性能分析入门指南

Python开发者  · 公众号  · Python  · 2016-11-29 22:04

正文

(点击 上方公众号 ,可快速关注)


英文:Huy Nguyen

译文:yexiaobai

链接:segmentfault.com/a/1190000000616798


虽然并非你编写的每个 Python 程序都要求一个严格的性能分析,但是让人放心的是,当问题发生的时候,Python 生态圈有各种各样的工具可以处理这类问题。


分析程序的性能可以归结为回答四个基本问题:


  1. 正运行的多快

  2. 速度瓶颈在哪里

  3. 内存使用率是多少

  4. 内存泄露在哪里


下面,我们将用一些神奇的工具深入到这些问题的答案中去。


用 time 粗粒度的计算时间


让我们开始通过使用一个快速和粗暴的方法计算我们的代码:传统的 unix time 工具。


$ time python yourprogram . py

real 0m1.028s

user 0m0.001s

sys 0m0.003s


三个输出测量值之间的详细意义在这里 stackoverflow article,但简介在这:


  • real — 指的是实际耗时

  • user — 指的是内核之外的 CPU 耗时

  • sys — 指的是花费在内核特定函数的 CPU 耗时


你会有你的应用程序用完了多少 CPU 周期的即视感,不管系统上其他运行的程序添加的系统和用户时间。


如果 sys 和 user 时间之和小于 real 时间,然后你可以猜测到大多数程序的性能问题最有可能与 IO wait 相关。


用 timing context 管理器细粒度的计算时间


我们下一步的技术包括直接嵌入代码来获取细粒度的计时信息。下面是我进行时间测量的代码的一个小片段


timer.py


import time

class 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 上下文管理器来包装你想计算的代码。当您的代码块开始执行,它将照顾启动计时器,当你的代码块结束的时候,它将停止计时器。


这个代码片段示例:


from timer import Timer

from redis import Redis

rdb = Redis ()

with Timer () as t :

rdb . lpush ( "foo" , "bar" )

print "=> elasped lpush: %s s" % t . secs

with Timer () as t :

rdb . lpop ( "foo" )

print "=> elasped lpop: %s s" % t . secs


为了使用它,使用 Python 的 with 关键字和 Timer 上下文管理器来包装你想计算的代码。当您的代码块开始执行,它将照顾启动计时器,当你的代码块结束的时候,它将停止计时器。


这个代码片段示例:


from timer import Timer

from redis import Redis

rdb = Redis ()

with Timer () as t :

rdb . lpush ( "foo" , "bar" )

print "=> elasped lpush: %s s" % t . secs

with Timer () as t :

rdb . lpop ( "foo" )

print "=> elasped lpop: %s s" % t . secs


为了看看我的程序的性能随着时间的演化的趋势,我常常记录这些定时器的输出到一个文件中。


使用 profiler 逐行计时和分析执行的频率


罗伯特·克恩有一个不错的项目称为 line_profiler , 我经常使用它来分析我的脚本有多快,以及每行代码执行的频率:


为了使用它,你可以通过使用 pip 来安装它:


pip install line_profiler


安装完成后,你将获得一个新模块称为 line_profiler 和 kernprof.py 可执行脚本。


为了使用这个工具,首先在你想测量的函数上设置 @profile 修饰符。不用担心,为了这个修饰符,你不需要引入任何东西。kernprof.py 脚本会在运行时自动注入你的脚本。


primes.py


@ profile

def 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 运行这个脚本。


kernprof . py - l - v fib . py


-l 选项告诉 kernprof 把修饰符 @profile 注入你的脚本,-v 选项告诉 kernprof 一旦你的脚本完成后,展示计时信息。这是一个以上脚本的类似输出:


Wrote profile results to primes . py . lprof

Timer unit : 1e - 06 s

File : primes . py

Function : primes at line 2

Total time : 0.00019 s

Line #      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







请到「今天看啥」查看全文