Python 高手编程系列八十八:微观分析

Python 高手编程系列八十八:微观分析 当找到慢速函数时有时需要进行更多的测试工作只测试程序的一部分。通过在速度测试中手动检测一部分代码来完成测试。例如可以从装饰器使用 cProfile 模块如下所示import tempfile, os, cProfile, pstatsdef profile(column‘time’, list5):… def _profile(function):… def __profile(*args, **kw):… s tempfile.mktemp()… profiler cProfile.Profile()… profiler.runcall(function,args, **kw)… profiler.dump_stats(s)… p pstats.Stats(s)… p.sort_stats(column).print_stats(list)… return __profile… returnprofile…from myapp import mainprofile(‘time’, 6)… def main_profiled():… return main()…main_profiled()Mon Apr 4 22:01:01 2016 /tmp/tmpvswuovz1207 function calls in 8.243 secondsOrdered by: internal timeList reduced from 7 to 6 due to restriction 6Ncalls tottime percall cumtime percall file:lineno(function)602 8.241 0.014 8.241 0.014 {built-in method sleep}400 0.001 0.000 4.026 0.010 myapp.py:5(medium)2 0.001 0.000 8.243 4.121 myapp.py:13(heavy)200 0.000 0.000 0.213 0.001 myapp.py:9(light)1 0.000 0.000 8.243 8.243 myapp.py:21(main)1 0.000 0.000 8.243 8.243 :1(main_profiled)from myapp import lightstats profile()(light)stats()Mon Apr 4 22:01:57 2016 /tmp/tmpnp_zk7dl3 function calls in 0.001 secondsOrdered by: internal timencalls tottime percall cumtime percall file:lineno(function)1 0.001 0.001 0.001 0.001 {built-in method sleep}1 0.000 0.000 0.001 0.001 myapp.py:9(light)这种方法允许测试应用程序的一部分并锐化统计输出。但在这个阶段有一个被调用者列表可能不是很有用因为已经指出该函数需要进行优化。唯一令人关注的信息是知道它有多快然后增强它。timeit 提供一种简单的方法来测量小代码片段的执行时间它使用主机系统提供的最佳底层计时器time.time 或 time.clock从而更好地满足这种需要如下所示from myapp import lightimport timeitt timeit.Timer(‘main()’)t.timeit(number5)10000000 loops, best of 3: 0.0269 usec per loop10000000 loops, best of 3: 0.0268 usec per loop10000000 loops, best of 3: 0.0269 usec per loop10000000 loops, best of 3: 0.0268 usec per loop10000000 loops, best of 3: 0.0269 usec per loop5.6196951866149902该模块可以被重复调用并且面向测试隔离的代码片段。这在应用程序上下文之外非常有用在命令提示符中例如在现有应用程序中使用不是很方便。但是应该谨慎使用 timeit 的结果。这是一个非常好的工具它可以客观地比较两段短代码但它也会让你很容易犯下危险的错误这将导致令人困惑的结论。这里例如通过 timeit 模块对两个无害的代码片段进行比较它可以使你认为通过加法的字符串连接比 str.join()方法更快$ python3 -m timeit -s ‘a map(str, range(1000))’ ‘“”.join(a)’1000000 loops, best of 3: 0.497 usec per loop$ python3 -m timeit -s ‘a map(str, range(1000)); s“”’ ‘for i in a: s i’10000000 loops, best of 3: 0.0808 usec per loop从第 2 章开始我们知道通过加法连接字符串不是一个好的模式。尽管有一些针对这种用法设计的一些较小的 CPython 微优化它最终会导致二次的运行时间。问题在于timeit命令行中-s 参数设置参数的细微差别以及 Python 3 中的范围如何工作。我不会讨论问题的细节但会把它留给你作为一个练习。总之在 Python 3 中这是正确的比较使用加法和 str.join()连接字符串的方法如下所示$ python3 -m timeit -s ‘a [str(i) for i in range(10000)]’ ‘s“”.join(a)’10000 loops, best of 3: 128 usec per loop$ python3 -m timeit -s ‘a [str(i) for i in range(10000)]’ ’s “”for i in a:s i’1000 loops, best of 3: 1.38 msec per loop测量 Pystones当测量执行时间时结果取决于计算机硬件。为了能够产生通用测量最简单的方法测量固定序列的代码基准速度并计算出它的比率。由此函数所花费的时间可以转换为一个比较通用的值可以在任何计算机上比较。Python 在其 test 包中提供了一个基准测试工具用于测量一个精选的操作序列的持续时间。结果是每秒钟计算机能够执行的 pystones 数量在现代硬件上通常约为一秒如下所示from test import pystonepystone.pystones()(1.0500000000000007, 47619.047619047589)该比率可用于将分析的持续时间转换为 pystones 的数量from test import pystonebenchtime, pystones pystone.pystones()def seconds_to_kpystones(seconds):… return (pystonesseconds) / 1000……seconds_to_kpystones(0.03)1.4563106796116512seconds_to_kpystones(1)48.543689320388381seconds_to_kpystones(2)97.087378640776762seconds_to_kpystones 返回千个 ρystones 的数量。如果你想编写一些速度断言这个转换可以包含在你的测试中。拥有 pystones 将允许你在测试中使用此装饰器以便于你在执行时间上设置断言。这些测试可以在任何计算机上运行并让开发人员避免速度回归。当应用程序的一部分已优化时他们将能够在测试中设置其最大执行时间并确保它不会被进一步的更改所破坏。这种方法当然不是理想的并且 100%准确的但是它至少比以硬编码的执行时间断言更好这些断言是以秒表示的原始值。