正如 Patashu 建议的那样,让我们使用 timeit 进行测试,而不是尝试猜测。我将在ipython 中使用魔法%timeit,因为它更简单。代码如下:
In [275]: def ff(): pass
In [276]: def ft(): pass
In [277]: def f1(b): # naive implementation
.....: for i in range(1000000):
.....: if b: ft()
.....: else: ff()
In [278]: %timeit f1(True)
10 loops, best of 3: 117 ms per loop
In [279]: def f2(b): # DSM's implementation
.....: f = ft if b else ff
.....: for i in range(1000000):
.....: f()
In [280]: %timeit f2(True)
10 loops, best of 3: 99.2 ms per loop
所以,它要快一点,至少在我的 Mac 上的 CPython 3.3.0 64 位中。
但是,如果您对 Python 优化有所了解,您可能会注意到,这与将全局变量移动到局部变量所期望的性能提升大致相同。所以,让我们在不提升布尔表达式的情况下做同样的事情,将其排除在外:
In [277]: def f3(b): # Just local binding, no if hoisting
.....: f, g = ft, ff
.....: for i in range(1000000):
.....: if b: f()
.....: else: g()
In [286]: %timeit f3(True)
10 loops, best of 3: 94.8 ms per loop
我整理了一个 more complete test,包括 OP 的预期优化,以及在 3.x 和 2.x 中工作的代码,无需更改,并针对 Apple 2.7.2、python.org 3.3.0、PyPy 1.9 运行它.0/2.7.2 和 Jython 2.5.2(所有 64 位都在 Mac 上构建,然后仅使用 Cython 0.17.1 pyximport(在 Python 3.3.0 下)编译与 Cython 代码相同的源代码:
3.3.0 2.7.2 PyPy Jython Cython
orig 1.136 1.519 0.091 1.680 0.448
OP optimization 1.119 1.362 0.034 1.613 0.460
rebinding 0.936 1.369 0.030 1.492 0.137
DSM version 0.936 1.329 0.031 1.523 0.138
因此,看起来在循环外绑定名称可以将速度提升 1.1 倍到 3 倍之间;另外,将比较提升到循环之外可能会给你另外 3% 左右的收益——但与使用 PyPy 代替 CPython、Cython 代替 Python,甚至 3.x 代替 2.x 相比,所有这些都微不足道。编写实际的 Cython 或自定义 C 代码,或将循环移至 numpy,会更快。
如果你仔细想想,这是有道理的。如果十亿个布尔比较或全局查找的成本很重要,那么十亿个函数调用和十亿个通过解释器的循环的成本将更加重要。如果您不想优化它(并且您通常可以通过使用生成器表达式、列表理解、map 调用等代替循环来做到这一点,即使切换解释器、围绕 @ 重写代码也是如此987654330@等不可行),你不应该担心小东西。
很明显,如果最后 3% 真的有影响,你需要在你真正关心的平台上执行更真实的测试。
可能值得使用 DSM 的实现,但因为它更惯用且更易于阅读,而不是因为它可能更快,也可能不会更快。