python的编译器故意地非常简单——这使得它快速且高度可预测。除了一些持续的折叠之外,它基本上生成了忠实地模仿源代码的字节码。已经有人建议了
dis
这确实是一个很好的方法来查看您得到的字节码——例如,如何
for i in [1, 2, 3]:
实际上不是不断地进行折叠,而是动态地生成文本列表,而
for i in (1, 2, 3):
(循环文本元组而不是文本列表)
是
能够持续折叠(原因:列表是一个可变的对象,为了保持“非常简单”的任务语句,编译器不需要检查这个特定的列表是否从未被修改过,所以
能够
优化成一个元组)。
所以这里有足够的空间进行手动微优化——尤其是提升。即重写
for x in whatever():
anobj.amethod(x)
作为
f = anobj.amethod
for x in whatever():
f(x)
保存重复的查找(编译器不检查
anobj.amethod
实际上可以改变
anobj
's bindings&c,以便下次需要新的查找--它只是做一些非常简单的事情,即不提升,这可以保证正确性,但绝对不能保证燃烧的速度;-)。
这个
timeit
模块(最好在shell提示imho中使用)使得测量编译和字节码解释的整体效果变得非常简单(只要确保要测量的代码片段没有影响计时的副作用,因为
timeit
做
在循环中反复运行;-)。例如:
$ python -mtimeit 'for x in (1, 2, 3): pass'
1000000 loops, best of 3: 0.219 usec per loop
$ python -mtimeit 'for x in [1, 2, 3]: pass'
1000000 loops, best of 3: 0.512 usec per loop
您可以看到重复列表构建的成本——并通过尝试一个小的调整来确认这确实是我们观察到的:
$ python -mtimeit -s'Xs=[1,2,3]' 'for x in Xs: pass'
1000000 loops, best of 3: 0.236 usec per loop
$ python -mtimeit -s'Xs=(1,2,3)' 'for x in Xs: pass'
1000000 loops, best of 3: 0.213 usec per loop
将Iterable的结构移动到
-s
设置(只运行一次,不定时)表明,元组上的循环速度稍快(可能10%),但第一对的大问题(列表速度比元组慢100%以上)主要与构造有关。
装备
时计
并且知道编译器在优化时故意非常简单,我们可以很容易地回答您的其他问题:
下列操作有多快
(相对)
* Function calls
* Class instantiation
* Arithmetic
* 'Heavier' math operations such as sqrt()
$ python -mtimeit -s'def f(): pass' 'f()'
10000000 loops, best of 3: 0.192 usec per loop
$ python -mtimeit -s'class o: pass' 'o()'
1000000 loops, best of 3: 0.315 usec per loop
$ python -mtimeit -s'class n(object): pass' 'n()'
10000000 loops, best of 3: 0.18 usec per loop
所以我们看到:实例化一个新样式的类和调用一个函数(都是空的)的速度大致相同,而实例化可能有一个很小的速度差,可能是5%;实例化一个旧样式的类的速度最慢(大约是50%)。微小的差别,如5%或更少当然可能是噪音,所以每次重复几次是明智的;但像50%的差别肯定远远超过噪音。
$ python -mtimeit -s'from math import sqrt' 'sqrt(1.2)'
1000000 loops, best of 3: 0.22 usec per loop
$ python -mtimeit '1.2**0.5'
10000000 loops, best of 3: 0.0363 usec per loop
$ python -mtimeit '1.2*0.5'
10000000 loops, best of 3: 0.0407 usec per loop
在这里我们看到:打电话
sqrt
比由运算符执行相同的计算慢(使用
**
通过调用一个空函数的成本来提高操作人员的能力;所有的算术操作人员的速度都大致相同,在噪声范围内(3或4纳秒的微小差别绝对是噪声;-)。检查持续折叠是否会干扰:
$ python -mtimeit '1.2*0.5'
10000000 loops, best of 3: 0.0407 usec per loop
$ python -mtimeit -s'a=1.2; b=0.5' 'a*b'
10000000 loops, best of 3: 0.0965 usec per loop
$ python -mtimeit -s'a=1.2; b=0.5' 'a*0.5'
10000000 loops, best of 3: 0.0957 usec per loop
$ python -mtimeit -s'a=1.2; b=0.5' '1.2*b'
10000000 loops, best of 3: 0.0932 usec per loop
…我们看到事实确实如此:如果将两个数字中的一个或两个都作为变量进行查找(这会阻止不断折叠),我们将支付“现实”成本。变量查找有其自身的成本:
$ python -mtimeit -s'a=1.2; b=0.5' 'a'
10000000 loops, best of 3: 0.039 usec per loop
不管怎样,当我们试图测量这么小的时间时,这绝不是微不足道的。的确
常数
查找也不是免费的:
$ python -mtimeit -s'a=1.2; b=0.5' '1.2'
10000000 loops, best of 3: 0.0225 usec per loop
如您所见,虽然比变量查找小,但它是相当可比的——大约有一半。
如果(有了仔细的分析和测量)您决定计算的一些核心非常需要优化,我建议您尝试
cython
--这是一个C/python合并,它尝试着像python一样整洁,像c一样快,虽然它不能100%达到目标,但它确实是一个很好的拳头(特别是,它使二进制代码比前代语言快得多,
pyrex
以及比它更富有一点)。对于性能的最后几个百分点,您可能仍然希望使用C(或者在某些特殊情况下使用汇编/机器代码),但这将是非常罕见的。