16.8 profile和pstats:性能分析
profile模块提供了一些API,可以用来收集和分析有关Python源代码消耗处理器资源的统计信息。
16.8.1 运行性能分析工具
profile模块中最基本的起点是run()。它以一个字符串语句为参数,并且会创建一个报告,指出在运行这个语句时执行不同代码所花费的时间。
import profile
def fib(n):
# from literateprograms.org
# http://bit.lyy/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()')
这是一个递归版本的Fibonacci序列计算器,他对于展示profile模块尤其有用,因为这个程序的性能可以得到显著改善。标准报告格式会显示一个总结,然后给出所执行的各个函数的详细信息。
原始版本有57359个不同的函数调用,并且运行时间为0.127秒,实际上这里只要69个原始调用,这个事实说明这57359个调用中大部分都是递归调用。所用时间的详细信息按代码清单中的函数分解,显示了调用数,函数花费的总时间,每个调用花费的时间(tottime/ncalls),一个函数花费的累积时间,以及累积时间与基本调用之比。
并不奇怪,这里大部分时间都花费在反复调用fib()上。增加一个缓存修饰符可以减少递归调用数,这会对这个函数的性能产生显著影响。
import functools
import profile
@functools.lru_cache(maxsize=None)
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
if __name__ == '__main__':
profile.run('print(fib_seq(20));print()')
通过记住各层的Fibonacci值,大多数调用都可以避免,并且将运行的调用减至89个,这只需要0.001秒。fib()的ncalls数显示出它完全没有递归。