8-18<X18>soundex.xml 性能优化
Contents
Great bug!
stage2 中采用的“目前最佳方案”是 1c,实际应为 1e。文中给出的代码也都是采用 1c 的方案,但实际文件中采用的是 1e 的方案。已修正。
stage 2 和 stage 3 中给出的 2c 的性能测试结果相差甚远。取 stage 2 中的结果,因为它比 stage 3 中的结果数值上均小,且这样处理后能稍微减轻测试结果与正文不符的情况。
多处对各程序性能的比较与作者给出的参考测试结果不符。具体可以参见下图(m$paint 画 di
)。黑色箭头代表文中所说的性能关系(a -> b <=> a 比 b 慢),反向的蓝色和红色箭头表示测试数据与其矛盾,旁边的数字表示测试点(1. Woo, 2. Pilgrim, 3. Flingjingwaller)其中蓝色表示矛盾,但两个数据本来相差就较小;而红色表示完全相反。
未修正。XiaQ 的测试结果也和正文不甚吻合。暂缀译注提醒读者。先慢慢联系 Mark Pigrim,等他重写本章后再同步修正。
18.1 概览
Para 5:哦,是的,单元测试。不必我说,在开始性能优化之前你需要一个完全的单元测试集。你需要的最后一件事情就是在乱动你的算法时引入新的问题。
你最不需要的就是在乱动你的算法时引入新的问题。
Para 6:With these caveats in place, let's look at some techniques for optimizing Python code. The code in question is an implementation of' the Soundex algorithm.
谨记着这些忠告,让我们来看一些优化 Python 代码的技术。我们要研究的代码是实施 Soundex 算法。
我们要研究的代码是 Soundex 算法的实现。
18.2 使用 timeit 模块
Para -2:Python 有一个方便的 min 函数可以把输入的列表返回成最小值:
Python 有一个方便的 min 函数返回输入列表中的最小值:
Para -1:如果你有一个很大的 Python 程序并且不知道你的性能问题所在,到
查看 hotshot 模块。
18.3 优化正则表达式
Para 1:Regular expressions are almost never the right answer; they should be avoided whenever possible. Not only for performance reasons, but simply because they're difficult to debug and maintain. 正则表达式几乎永远不是最好的答案,而且应该被尽可能避开。这不仅仅是基于性能考虑,而是因为差错
和维护都很困难,……
调试
Para 4:How does soundex1a.py perform? For convenience, the __main__ section of the script contains this code that calls the timeit module, sets up a timing test with three different names, tests each name three times, and displays the minimum time for each:
soundex1a.py 表现如何?为了方便,__main__ 部分的代码包含了调用 timeit 模块,建立一个分别测试三个不同名字三次并显示最短耗时的一个计时测试代码:
soundex1a.py 表现如何?为了方便,__main__ 部分包含了一段代码:调用 timeit 模块,为三个不同名字分别建立测试,依次测试,并显示每个测试的最短耗时:
Para 7:Woo 是个……小样本;Pilgrim 是个……正常样本;Flingjingwaller 是……特别长的样本。其它的测试可能同样有帮助,但它们已经是很好的不同样本范围了。
但它们已经很好地代表了不同的样本范围。
Para -7:But is this the wrong path? The logic here is simple: the input source needs to be non-empty, and it needs to be composed entirely of letters. Wouldn't it be faster to write a loop checking each character, and do away with regular expressions altogether? 但是这样的优化是正路吗?这里的逻辑很简单:输入 source 应该是非空,并且需要完全由字母构成。如果编写一个循环查看每个字符并且与正则表达式一同工作是否会更快些?
如果编写一个循环查看每个字符并且抛弃正则表达式,是否会更快些?
18.4 优化字典查找
pass
18.5 优化列表操作
Para 6:soundex3a.py does not run any faster than soundex2c.py, and may even be slightly slower (although it's not enough of a difference to say for sure):
soundex3a.py 并不比 soundex2c.py 运行得快多少,而且甚至
可能会更慢些(差异还没有大到可以确信这一点):
有趣的是,结果表明 soundex3a.py 并不比 soundex2c.py 慢 (快那么一点点),不这样加就说不同了。
Para 7:为什么 soundex3a.py 不更快呢?其实 Python 的索引功能恰恰很有效。重复使用 digits2[-1] 根本没什么问题。另一方面,手工另外保留上一个数字为另外的变量意味着我们存储的每个数字的 两个 变量赋值,这便抹杀了我们避开索引查找所带来的微小好处。
另一方面,手工保留上一个数字意味着我们每存储一个数字都要为 两个 变量赋值……
Para -4: Instead of incrementally building a new list (or string) out of the source string, why not move elements around within a single list? 与其一砖一瓦地建造一个新的列表(或者字符串),为什么不在一个列表中移动元素?
其一砖一瓦地建造一个新的列表(或者字符串),为什么不选择操作列表的元素呢?
就下文使用的方法来看,move around 指的仅仅是一般意义的“改动”,而不是“移动”。
Para -1:我们在这儿除了试用了几种 “clever
” 的技术,根本没有什么进步。
聪明
18.6 优化字符串操作
Para -5:我们早就知道我们需要把结果截成四字符,并且我们知道我们已经有了一个没有改变的字符(起始字符从 source 中不作改变的拿过来)。这意味着我们可以仅仅在输出的结尾添加三个零,然后截短它。
我们早就知道我们需要把结果截成四字符,并且我们知道我们已经有了至少一个字符(直接从 source 中拿过来的起始字符)。这意味着我们可以仅仅在输出的结尾添加三个零,然后截断它。
18.7 小结
Para -1:最后一点太重要了,这章中你令这个程序三倍的提速并且另百万次的调用节省 20 秒。
最后一点太重要了,这章中你令这个程序提速三倍并且令百万次的调用节省 20 秒。