python的程序分析
看完Python程序员必知必会的开发者工具 记录一下
profile模块和cProfile模块可以用来分析程序。它们的工作原理都一样,唯一的区别是,cProfile模块是以C扩展的方式实现的,如此一来运行的速度也快了很多,也显得比较流行。这两个模块都可以用来收集覆盖信息(比如,有多少函数被执行了),也能够收集性能数据。对一个程序进行分析的最简单的方法就是运行这个命令:
python -m cProfile someprogram.py
此外,也可以使用profile模块中的run函数:
run(command [, filename])
该函数会使用exec语句执行command中的内容。filename是可选的文件保存名,如果没有filename的话,该命令的输出会直接发送到标准输出上。
下面是分析器执行完成时的输出报告:
126 function calls (6 primitive calls) in 5.130 CPU seconds Ordered by: standard name ncalls tottime percall cumtime percall filename:lineno(function) 1 0.030 0.030 5.070 5.070 <string>:1(?) 121/1 5.020 0.041 5.020 5.020 book.py:11(process) 1 0.020 0.020 5.040 5.040 book.py:5(?) 2 0.000 0.000 0.000 0.000 exceptions.py:101(_ _init_ _) 1 0.060 0.060 5.130 5.130 profile:0(execfile('book.py')) 0 0.000 0.000 profile:0(profiler)
当输出中的第一列包含了两个数字时(比如,121/1),后者是元调用(primitive call)的次数,前者是实际调用的次数(译者注:只有在递归情况下,实际调用的次数才会大于元调用的次数,其他情况下两者都相等)。对于绝大部分的应用程序来讲使用该模块所产生的的分析报告就已经足够了,比如,你只是想简单地看一下你的程序花费了多少时间。然后,如果你还想将这些数据保存下来,并在将来对其进行分析,你可以使用pstats模块。
假设你想知道你的程序究竟在哪里花费了多少时间。
如果你只是想简单地给你的整个程序计时的话,使用Unix中的time命令就已经完全能够应付了。例如:
bash % time python3 someprogram.py
real 0m13.937s
user 0m12.162s
sys 0m0.098s
bash %
通常来讲,分析代码的程度会介于这两个极端之间。比如,你可能已经知道你的代码会在一些特定的函数中花的时间特别多。针对这类特定函数的分析,我们可以使用修饰器decorator,例如:
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
使用decorator的方式很简单,你只需要把它放在你想要分析的函数的定义前面就可以了。例如:
>>> @timethis ... def countdown(n): ... while n > 0: ... n -= 1 ... >>> countdown(10000000) __main__.countdown : 0.803001880645752 >>>
如果想要分析一个语句块的话,你可以定义一个上下文管理器(context manager)。例如:
import time from contextlib import contextmanager @contextmanager def timeblock(label): start = time.perf_counter() try: yield finally: end = time.perf_counter() print('{} : {}'.format(label, end - start))
接下来是如何使用上下文管理器的例子:
>>> with timeblock('counting'): ... n = 10000000 ... while n > 0: ... n -= 1 ... counting : 1.5551159381866455 >>>
如果想研究一小段代码的性能的话,timeit模块会非常有用。例如:
>>> from timeit import timeit >>> timeit('math.sqrt(2)', 'import math') 0.1432319980012835 >>> timeit('sqrt(2)', 'from math import sqrt') 0.10836604500218527 >>>
timeit的工作原理是,将第一个参数中的语句执行100万次,然后计算所花费的时间。第二个参数指定了一些测试之前需要做的环境准备工作。如果你需要改变迭代的次数,可以附加一个number参数,就像这样:
>>> timeit('math.sqrt(2)', 'import math', number=10000000) 1.434852126003534 >>> timeit('sqrt(2)', 'from math import sqrt', number=10000000) 1.0270336690009572 >>>
当进行性能评估的时候,要牢记任何得出的结果只是一个估算值。函数time.perf_counter()能够在任一平台提供最高精度的计时器。然而,它也只是记录了自然时间,记录自然时间会被很多其他因素影响,比如,计算机的负载。如果你对处理时间而非自然时间感兴趣的话,你可以使用time.process_time()。例如:
import time from functools import wraps def timethis(func): @wraps(func) def wrapper(*args, **kwargs): start = time.process_time() r = func(*args, **kwargs) end = time.process_time() print('{}.{} : {}'.format(func.__module__, func.__name__, end - start)) return r return wrapper
最后也是相当重要的就是,如果你想做一个详细的性能评估的话,你最好查阅time,timeit以及其他相关模块的文档,这样你才能够对平台相关的不同之处有所了解。
profile模块中最基础的东西就是run()函数了。该函数会把一个语句字符串作为参数,然后在执行语句时生成所花费的时间报告。
import profile def fib(n): # from literateprograms.org # http://bit.ly/hlOQ5m if n == 0: return 0 elif n == 1: return 1 else: return fib(n-1) + fib(n-2) def fib_seq(n): seq = [] if n > 0: seq.extend(fib_seq(n-1)) seq.append(fib(n)) return seq profile.run('print(fib_seq(20)); print')