- 内容提要
- 前言
- 作者简介
- 封面简介
- 第1章 理解高性能 Python
- 第2章 通过性能分析找到瓶颈
- 2.1 高效地分析性能
- 2.2 Julia 集合的介绍
- 2.3 计算完整的 Julia 集合
- 2.4 计时的简单方法——打印和修饰
- 2.5 用 UNIX 的 time 命令进行简单的计时
- 2.6 使用 cProfile 模块
- 2.7 用 runsnakerun 对 cProfile 的输出进行可视化
- 2.8 用 line_profiler 进行逐行分析
- 2.9 用 memory_profiler 诊断内存的用量
- 2.10 用 heapy 调查堆上的对象
- 2.11 用 dowser 实时画出变量的实例
- 2.12 用 dis 模块检查 CPython 字节码
- 2.13 在优化期间进行单元测试保持代码的正确性
- 2.14 确保性能分析成功的策略
- 2.15 小结
- 第3章 列表和元组
- 第4章 字典和集合
- 第5章 迭代器和生成器
- 第6章 矩阵和矢量计算
- 第7章 编译成 C
- 第8章 并发
- 第9章 multiprocessing 模块
- 第10章 集群和工作队列
- 第11章 使用更少的 RAM
- 第12章 现场教训
2.13 在优化期间进行单元测试保持代码的正确性
如果你不对你的代码进行单元测试,那么从长远来看你可能正在损害你的生产力。Ian(脸红)十分尴尬地提到有一次他花了一整天的时间优化他的代码,因为嫌麻烦所以他禁用了单元测试,最后却发现那个显著的速度提升只是因为他破坏了需要优化的那段算法。这样的错误你一次都不要犯。
除了单元测试,你还应该坚定地考虑使用coverage.py。它会检查有哪些代码行被你的测试所覆盖并找出那些没有被覆盖的代码。这可以让你迅速知道你是否测试了你想要优化的代码,那么在优化过程中可能潜伏的任何错误都会被迅速抓出来。
No-op的@profile修饰器
如果你的代码使用了line_profiler或者memory_profiler的@profile修饰器,那么你的单元测试会引发一个NameError异常并失败。原因是单元测试框架不会将@profile修饰器注入本地名字空间。no-op修饰器可以在这种时候解决问题。在你测试时把它加入你的代码块,并在你结束测试后移除它是在方便不过的事情了。
使用no-op修饰器,你可以运行你的测试而不需要修改你的代码。这意味着你可以在每次优化之后都运行你的测试,你将永远不会倒在一个出问题的优化步骤上。
如例2-20所示,假设我们有一个ex.py模块,它有一个测试用例(基于nosetests框架)和一个函数,这个函数我们正在用line_profiler或者memory_profiler进行性能分析。
例2-20 一个简单的函数和一个测试用例需要用到@profile
# ex.py import unittest @profile def some_fn(nbr): return nbr * 2 class TestCase(unittest.TestCase): def test(self): result = some_fn(2) self.assertEquals(result, 4)
如果我们运行nosetests测试我们的代码就会得到一个NameError:
$ nosetests ex.py E ====================================================================== ERROR: Failure: NameError (name 'profile' is not defined) ... NameError: name 'profile' is not defined Ran 1 test in 0.001s FAILED (errors=1)
解决方法是在ex.py开头添加一个no-op修饰器(你可以在完成性能分析之后移除它)。如果在名字空间中寻找不到@profile修饰器(因为没有使用line_profiler或者memory_profiler),那么我们写的no-op版本的修饰器就会被加入名字空间。如果line_profiler或者memory_profiler已经将新的函数加入名字空间,那么我们no-op版本的修饰器就会被忽略。
对于line_profiler,我们可以加入例2-21的代码。
例2-21 在单元测试时在名字空间中加入针对line_profiler的no-op@profile修饰器
# line_profiler if '__builtin__' not in dir() or not hasattr(__builtin__, 'profile'): def profile(func): def inner(*args, **kwargs): return func(*args, **kwargs) return inner
__builtin__检查是针对nosetests的,hasattr则用来检查@profile修饰器是否已经被加入名字空间。现在可以在我们的代码上成功运行nosetests了:
$ kernprof.py -v -l ex.py Line # Hits Time Per %%HTMLit % Time Line Contents ============================================================== 11 @profile 12 def some_fn(nbr): 13 1 3 3.0 100.0 return nbr * 2 $ nosetests ex.py . Ran 1 test in 0.000s
对于memory_profiler,我们使用例2-22的代码。
例2-22 在单元测试时在名字空间中加入针对memory_profiler的no-op@profile修饰器
# memory_profiler if 'profile' not in dir(): def profile(func): def inner(*args, **kwargs): return func(*args, **kwargs) return inner
期望产生的输出如下:
python -m memory_profiler ex.py ... Line # Mem usage Increment Line Contents ================================================ 11 10.809 MiB 0.000 MiB @profile 12 def some_fn(nbr): 13 10.809 MiB 0.000 MiB return nbr * 2 $ nosetests ex.py . Ran 1 test in 0.000
不使用这些修饰器可以节省你几分钟,但是一旦你在一个破坏你代码的错误优化上失去了好几个小时,你就会想要把这个加入你的工作流程了。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论