- 内容提要
- 前言
- 作者简介
- 封面简介
- 第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章 现场教训
6.6 告诫故事:验证你的优化(scipy)
重要的是记住我们在本章中对每一次优化使用的一套方法:首先对代码进行性能分析来感知问题可能出在哪,提出一个可能的解决方案来修改慢的那部分,然后再次进行性能分析来确保我们的修改可行。虽然这听上去十分简单直接,但事情很快会变得复杂,比如我们看到numexpr的性能是如何受到矩阵大小影响的。
当然我们提出的方案并不总是可行。在为本章写代码时,有一次作者看到laplacian函数最慢并假设scipy的函数会明显更快。这一想法来自于一个事实,那就是laplacian在图像分析这一行是一个常见操作,可能已经有一个非常优化的库加速了其运行的速度。scipy正好有一个图像子模块,我们一定会得到幸运的眷顾!
用scipy实现起来很简单(例6-21)且不需要考虑实现周期边界的复杂度(因为scipy的wrap模式会帮我们搞定)。
例6-21 使用scipy的laplace滤波
from scipy.ndimage.filters import laplace def laplacian(grid, out): laplace(grid, out, mode='wrap')
能够简单实现这一点相当重要,且绝对为这个函数赢得了一些分数,在我们考查性能之前。然而一旦我们对scipy代码进行性能对比(例6-22),我们才发觉:这个函数跟之前的相比(例6-14)并没有带来大量的速度提升。事实上,当我们增加矩阵大小时,这个函数甚至开始变得性能下降了(见本章最后的图6-4)。
例6-22 使用scipy的laplacian函数的扩散代码性能指标
$ perf stat -e cycles,stalled-cycles-frontend,stalled-cycles-backend,instructions,\ cache-references,cache-misses,branches,branch-misses,task-clock,faults,\ minor-faults,cs,migrations -r 3 python diffusion_scipy.py Performance counter stats for 'python diffusion_scipy.py' (3 runs): 6,573,168,470 cycles # 2.929 GHz 3,574,258,872 stalled-cycles-frontend # 54.38% frontend cycles idle 2,357,614,687 stalled-cycles-backend # 35.87% backend cycles idle 9,850,025,585 instructions # 1.50 insns per cycle # 0.36 stalled cycles per insn 415,930,123 cache-references # 185.361 M/sec 3,188,390 cache-misses # 0.767 % of all cache refs 1,608,887,891 branches # 717.006 M/sec 4,017,205 branch-misses # 0.25% of all branches 2243.897843 task-clock # 0.994 CPUs utilized 7,319 page-faults # 0.003 M/sec 7,319 minor-faults # 0.003 M/sec 12 context-switche # 0.005 K/sec 1 CPU-migrations # 0.000 K/sec 2.258396667 seconds time elapsed
对比scipy版本和我们自己的laplacian函数性能指标(例6-18),我们可以得到一些提示,为什么没有从这次重写中得到期待的性能提升。
指标中最突出的是page-faults和instructions。scipy版本的指标中两者的值都明显更大。page-faults的增加显示出scipy的laplace函数虽然支持就地操作,但它依然分配了很多内存。事实上scipy版本的page-faults的次数比我们第一次用numpy重写的版本还要多(例6-15)。
但更严重的还是instructions指标。它告诉我们scipy代码比我们的laplacian函数让CPU多做了超过两倍的工作。即使这些指令都是更加优化的(因为我们可以看到更高的insns per cycle计数,也就是CPU在一个时钟周期内能完成多少条指令),额外的优化并没有能胜过指令数的猛增。这可能部分是因为scipy代码写得非常通用,以使它可以处理具有不同边界条件的各种输入(所以需要额外的代码也就是更多的指令数)。事实上这一点我们也可以通过scipy代码拥有更多的branches指标看出。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论