10.8 Victor Stinner访谈

Victor是资深的Python黑客,许多Python模块的核心贡献者和作者。他最近撰写了PEP 454(https://www.python.org/dev/peps/pep-0454/),其中提出了一个新的tracemalloc模块,用于在Python中跟踪内存块的分配,并写了一个简单的AST优化器。

阅读 ‧ 电子书库

广告:个人专属 VPN,独立 IP,流量大,速度快,连接稳定,多机房切换,每月最低仅 10 美元

优化Python代码的一个初步策略是什么?

针对Python的策略其实和在其他语言中一样。首先需要定义良好的用例,以得到一个稳定可重现的基准。没有可靠基准的情况下尝试不同的优化方法很可能导致时间的浪费和不成熟的优化。无用的优化可能使代码更糟,更不易懂,甚至更慢。有用的优化必须至少让程序加速5%。

如果发现代码的某个部分比较“慢”,那么需要针对这段代码设计一个基准测试。对于较短的函数的基准测试通常称为“微基准测试”。通过微基准测试衡量优化效果时,速度提升应该至少达到20%或者25%。

在不同的计算机、不同的操作系统甚至不同的编译器上运行同一个基准测试会很有意思。例如,函数realloc()的性能在Linux和Windows上是不同的。有时候针对不同的平台的代码实现也可能会不同,尽管这是应该尽量避免的。

关于Python代码的性能分析和优化有许多不同的工具,你最喜欢的是哪个?

Python 3.3提供了一个新的time.perf_counter()函数,用来为基准测试衡量已耗用时间。它是最好的解决方案。

测试应该运行不止一次,最少3次,5次基本够了。重复测试可以填充磁盘缓存和CPU缓存。我倾向于保证最小时间,其他一些开发人员则倾向于使用几何平均值。

对于微基准测试,timeit模块简单易用且能很快得到结果,但使用默认的参数结果并不稳定。应该手工重复测试,以得到稳定的结果。

优化是非常花时间的,所以最好能专注那些耗费最多CPU的函数。为了找到这些函数,Python提供了cProfile和用来记录每个函数时间消耗的profile模块。

能够改进性能的最有意思的Python技巧是什么?

应该尽可能重用标准库。它们经过良好的测试并且通常都很高效。Python内置的类型都是用C实现的,所以性能都很好。应使用正确的容器以得到最佳的性能,Python提供许多不同的容器,如dictlistdequeset等。

也有一些用来优化Python的非常手段,但是应该避免使用它们,因为这一点点的速度提升会丧失代码的可读性。

Python之禅(PEP 20)说:“应该有一种——最好只有一种——显而易见的方式去实现。”实际上,写Python代码有很多不同的方式,且性能各异,所以只能信赖针对特定用例的基准测试。

在哪些领域中Python的性能很差?哪些领域中应该小心使用?

通常,在开发新的应用程序时我不太担心性能问题。不成熟的优化是所有问题之源。当找到了缓慢的函数时,应该修改算法。如果算法和容器都是经过仔细挑选的,那么可以考虑用C语言重写短函数以获得更好的性能。

CPython的一个众所周知的性能瓶颈是全局解释器锁(Global Interpreter Lock,GIL)。两个线程不能在同时执行Python字节码。然而,这个限制只在两个线程执行纯Python代码时有影响。如果大多数处理时间花在函数调用上,并且这些函数释放了GIL,那么GIL并非性能瓶颈。例如,大多数I/O函数都会释放GIL。

multiprocessing模块可以很容易地用来绕过GIL。另一个稍微复杂的方式是编写异步代码。Twisted、Tornado和Tulip都是利用了这一技术的面向网络的库。

你见过最多的导致性能差的“错误”是什么?

没有很好地理解Python就可能写出效率低的代码。例如,我见过在不需要复制时错误地使用了copy.deepcopy()

另一个性能杀手是低效的数据结构。少于100个元素的情况下,容器类型对性能没有影响。对于更多元素的场景,应该了解每个操作(addgetdelete)的复杂性和影响。


1例如,小于PyTuple_MAXSAVESIZE(默认是20)的元组在CPython中会使用更快的内存分配器。
2假设整个字符串在一个连续的内存区域。