2011-11-09 68 views
79

同时优化我的代码实现了以下内容:Python:为什么*和**比/和sqrt()更快?

>>> from timeit import Timer as T 
>>> T(lambda : 1234567890/4.0).repeat() 
[0.22256922721862793, 0.20560789108276367, 0.20530295372009277] 
>>> from __future__ import division 
>>> T(lambda : 1234567890/4).repeat() 
[0.14969301223754883, 0.14155197143554688, 0.14141488075256348] 
>>> T(lambda : 1234567890 * 0.25).repeat() 
[0.13619112968444824, 0.1281130313873291, 0.12830305099487305] 

也:

>>> from math import sqrt 
>>> T(lambda : sqrt(1234567890)).repeat() 
[0.2597470283508301, 0.2498021125793457, 0.24994492530822754] 
>>> T(lambda : 1234567890 ** 0.5).repeat() 
[0.15409398078918457, 0.14059877395629883, 0.14049601554870605] 

我认为它与Python是用C语言实现的方式做,但我不知道是否有人会关心解释为什么如此?

+0

你接受你的问题的答案(我假设你回答你真正的问题)与你的问题题目没有多大关系。你可以编辑它与持续折叠有关吗? –

+1

@ZanLynx - 嗨。你介意澄清一下吗?我发现问题标题恰恰表达了我想知道的内容(为什么X比Y更快),并且我选择的答案正是这样的......看起来与我完美匹配......但也许我忽略了某些东西? – mac

+8

乘法和幂函数总是比division和sqrt()函数更快,因为它们的性质。除法和根操作通常必须使用一系列更精细更精确的近似值,并且不能像乘法那样直接转到正确的答案。 –

回答

112

你的结果(某种意料之外的原因)是Python似乎将涉及浮点乘法和幂乘的​​常量表达式折叠起来,而不是分割。 math.sqrt()是一个完全不同的野兽,因为它没有字节码,它涉及到一个函数调用。

关于Python 2.6.5,下面的代码:

x1 = 1234567890.0/4.0 
x2 = 1234567890.0 * 0.25 
x3 = 1234567890.0 ** 0.5 
x4 = math.sqrt(1234567890.0) 

编译下面的字节码:

# x1 = 1234567890.0/4.0 
    4   0 LOAD_CONST    1 (1234567890.0) 
       3 LOAD_CONST    2 (4.0) 
       6 BINARY_DIVIDE  
       7 STORE_FAST    0 (x1) 

    # x2 = 1234567890.0 * 0.25 
    5   10 LOAD_CONST    5 (308641972.5) 
      13 STORE_FAST    1 (x2) 

    # x3 = 1234567890.0 ** 0.5 
    6   16 LOAD_CONST    6 (35136.418286444619) 
      19 STORE_FAST    2 (x3) 

    # x4 = math.sqrt(1234567890.0) 
    7   22 LOAD_GLOBAL    0 (math) 
      25 LOAD_ATTR    1 (sqrt) 
      28 LOAD_CONST    1 (1234567890.0) 
      31 CALL_FUNCTION   1 
      34 STORE_FAST    3 (x4) 

正如你所看到的,乘法和乘方采取任何时候都因为他们”代码编译完成后重新执行。由于它在运行时发生,分区需要更长时间平方根不仅是这四者中计算量最大的操作,还会产生其他各种开销(属性查询,函数调用等)。

如果您消除常量折叠的效果,几乎没有分开乘法和除法:因为它是后者的一个特例比x ** 0.5快一点点

In [16]: x = 1234567890.0 

In [17]: %timeit x/4.0 
10000000 loops, best of 3: 87.8 ns per loop 

In [18]: %timeit x * 0.25 
10000000 loops, best of 3: 91.6 ns per loop 

math.sqrt(x)实际上,据推测,因此可以更有效地完成,尽管开销:

In [19]: %timeit x ** 0.5 
1000000 loops, best of 3: 211 ns per loop 

In [20]: %timeit math.sqrt(x) 
10000000 loops, best of 3: 181 ns per loop 

编辑2011-11-16:常量表达式折叠由Python的PE进行Ephole优化器。的源代码(peephole.c)包含以下评论解释了为什么常数除法没有被折叠:

case BINARY_DIVIDE: 
     /* Cannot fold this operation statically since 
      the result can depend on the run-time presence 
      of the -Qnew flag */ 
     return 0; 

-Qnew标志使“真师”在PEP 238定义。

+2

也许它正在“保护”除以零? – hugomg

+2

@missingno:我不清楚为什么这样的“保护”是必要的,因为这两个参数在编译时已知,结果是(+ inf,-inf,NaN之一)。 – NPE

+1

也许'from __future__ import division'测试使用了类似的方法。 – Simon

相关问题