为什么加法和乘法比比较更快?

dan*_*gom 21 python performance

我一直认为比较是计算机可以执行的最快的操作.我记得在D. Knuth的演讲中听到它,他在那里按降序编写循环"因为与0的比较很快".我还读到乘法应该比这里的加法慢.

我很惊讶地看到,在Python 2和Mac下,在Linux和Mac下都进行了测试,比较似乎比算术运算慢得多.

谁有人解释为什么?

%timeit 2 > 0
10000000 loops, best of 3: 41.5 ns per loop

%timeit 2 * 2
10000000 loops, best of 3: 27 ns per loop

%timeit 2 * 0
10000000 loops, best of 3: 27.7 ns per loop

%timeit True != False
10000000 loops, best of 3: 75 ns per loop

%timeit True and False
10000000 loops, best of 3: 58.8 ns per loop
Run Code Online (Sandbox Code Playgroud)

并在python 3下:

$ ipython3
Python 3.5.2 | packaged by conda-forge | (default, Sep  8 2016, 14:36:38) 
Type "copyright", "credits" or "license" for more information.

IPython 5.1.0 -- An enhanced Interactive Python.
?         -> Introduction and overview of IPython's features.
%quickref -> Quick reference.
help      -> Python's own help system.
object?   -> Details about 'object', use 'object??' for extra details.

In [1]: %timeit 2 + 2
10000000 loops, best of 3: 22.9 ns per loop

In [2]: %timeit 2 * 2
10000000 loops, best of 3: 23.7 ns per loop

In [3]: %timeit 2 > 2
10000000 loops, best of 3: 45.5 ns per loop

In [4]: %timeit True and False
10000000 loops, best of 3: 62.8 ns per loop

In [5]: %timeit True != False
10000000 loops, best of 3: 92.9 ns per loop
Run Code Online (Sandbox Code Playgroud)

mu *_*u 無 19

这是由于Python编译器中的Peep Hole 优化器中的常量折叠而发生的.

使用dis模块,如果我们打破每个语句以查看它们在机器级别的翻译方式,你会发现所有运算符如不等式,等式等首先被加载到内存中然后进行评估.但是,计算所有表达式,如乘法,加法等,并将其作为常量加载到内存中.

总的来说,这会导致执行步骤的数量减少,从而加快了步骤:

>>> import dis

>>> def m1(): True != False
>>> dis.dis(m1)
  1           0 LOAD_GLOBAL              0 (True)
              3 LOAD_GLOBAL              1 (False)
              6 COMPARE_OP               3 (!=)
              9 POP_TOP             
             10 LOAD_CONST               0 (None)
             13 RETURN_VALUE        

>>> def m2(): 2 *2
>>> dis.dis(m2)
  1           0 LOAD_CONST               2 (4)
              3 POP_TOP             
              4 LOAD_CONST               0 (None)
              7 RETURN_VALUE        

>>> def m3(): 2*5
>>> dis.dis(m3)
  1           0 LOAD_CONST               3 (10)
              3 POP_TOP             
              4 LOAD_CONST               0 (None)
              7 RETURN_VALUE        

>>> def m4(): 2 > 0
>>> dis.dis(m4)
  1           0 LOAD_CONST               1 (2)
              3 LOAD_CONST               2 (0)
              6 COMPARE_OP               4 (>)
              9 POP_TOP             
             10 LOAD_CONST               0 (None)
             13 RETURN_VALUE        

>>> def m5(): True and False
>>> dis.dis(m5)
  1           0 LOAD_GLOBAL              0 (True)
              3 JUMP_IF_FALSE_OR_POP     9
              6 LOAD_GLOBAL              1 (False)
        >>    9 POP_TOP             
             10 LOAD_CONST               0 (None)
             13 RETURN_VALUE        
Run Code Online (Sandbox Code Playgroud)

  • 那么,看[peephole.c](https://hg.python.org/cpython/file/tip/Python/peephole.c#l510)看起来有不同的数学操作的不同操作码,但没有这样的东西不同比较运算符,只有`COMPARE_OP`.也许折叠常数会涉及访问不易访问的信息? (4认同)
  • 我很惊讶窥视孔优化器不会减少已知的条件,特别是考虑到它会允许死剥离分支。善用DIS (2认同)

Ton*_*nyK 8

正如其他人所解释的那样,这是因为Python的窥视孔优化器优化了算术运算而不是比较.

为Basic编译器编写了自己的窥孔优化器之后,我可以向您保证,优化常量比较与优化常量算术运算一样简单.所以没有技术上的理由说明为什么Python应该做后者而不是前者.

但是,每个这样的优化都必须单独编程,并且需要两个成本:编程它的时间,以及占用Python可执行文件空间的额外优化代码.因此,您发现自己必须进行一些分类:哪些优化足以使其值得花费?

似乎Python的实现者,合理地决定首先优化算术运算.也许他们将在未来的版本中进行比较.

  • 值得注意的是,在python 2中,"True!= False"和"True and False"操作无法以这种方式进行优化,因为可以重新定义"True"和"False",因此需要查看这些全局变量每次执行时都会启动.在python 3中,"True"和"False"是关键字,无法重新定义. (2认同)