Python 为什么加法和乘法比比较快?
我一直认为比较是计算机能执行的最快的操作。我记得在D.Knuth的一次演讲中听到过这句话,当时他按降序写循环,“因为与0的比较很快”。我还读到乘法应该比加法慢 我很惊讶地看到,在Python2和Python3中,在Linux和Mac下进行测试时,比较似乎比算术运算慢得多 有人能解释一下原因吗Python 为什么加法和乘法比比较快?,python,performance,Python,Performance,我一直认为比较是计算机能执行的最快的操作。我记得在D.Knuth的一次演讲中听到过这句话,当时他按降序写循环,“因为与0的比较很快”。我还读到乘法应该比加法慢 我很惊讶地看到,在Python2和Python3中,在Linux和Mac下进行测试时,比较似乎比算术运算慢得多 有人能解释一下原因吗 %timeit 2 > 0 10000000 loops, best of 3: 41.5 ns per loop %timeit 2 * 2 10000000 loops, best of 3:
%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
在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
快速显示比较涉及更多操作。根据,()对乘法、加法等进行了一些预计算,但没有对比较运算符进行预计算:
>>> import dis
>>> def a():
... return 2*3
...
>>> dis.dis(a)
2 0 LOAD_CONST 3 (6)
3 RETURN_VALUE
>>> def b():
... return 2 < 3
...
>>> dis.dis(b)
2 0 LOAD_CONST 1 (2)
3 LOAD_CONST 2 (3)
6 COMPARE_OP 0 (<)
9 RETURN_VALUE
导入dis
>>>定义a():
... 返回2*3
...
>>>dis.dis(a)
2负载常数3(6)
3返回值
>>>def b():
... 返回2<3
...
>>>dis.dis(b)
2 0负载常数1(2)
3负载常数2(3)
6 COMPARE_OP 0(这是由于Python编译器内部的错误而发生的
使用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
正如其他人所评论的,这是由于窥视孔优化器预先计算了2*3(6)的结果
0 LOAD_CONST 3 (6)
但是试试这个-它将禁止优化器预计算结果
>>> def a(a, b):
... return a*b
...
>>> dis.dis(a)
2 0 LOAD_FAST 0 (a)
3 LOAD_FAST 1 (b)
6 BINARY_MULTIPLY
7 RETURN_VALUE
>>> def c(a,b):
... return a<b
...
>>> dis.dis(c)
2 0 LOAD_FAST 0 (a)
3 LOAD_FAST 1 (b)
6 COMPARE_OP 0 (<)
9 RETURN_VALUE
>>>
定义a(a,b):
…返回a*b
...
>>>dis.dis(a)
2 0加载速度0(a)
3加载速度1(b)
6二进制乘法
7返回值
>>>def c(a、b):
…返回一个>>dis.dis(c)
2 0加载速度0(a)
3加载速度1(b)
6比较操作0(>>
如果对这些函数计时,比较会更快。对于python,上述答案是正确的。对于更复杂的机器代码,我假设我们在这里讨论的是整数运算,对于浮点数和复杂对象,以下任何一项都不适用。此外,我们假设您正在比较的值已经加载进入寄存器。如果他们不从任何地方获取它们,可能需要比实际操作长100倍的时间 现代的CPU有几种比较两个数字的方法。非常流行的方法是,如果你只想知道两个值是否相等,就执行XOR a、b;如果你想知道值之间的关系(小、大、等),就执行CMP a、b.CMP操作只是一个减法运算,结果被丢弃,因为我们只对运算后的标志感兴趣 这两个操作的深度都是1,因此它们可以在单个CPU周期内执行。这是最快的速度。乘法是一种重复加法的形式,因此操作的深度通常等于寄存器的大小。可以进行一些优化以减少深度,但通常是乘法是CPU可以执行的较慢的操作之一 但是,乘以0,1或2的任意幂可以简化为移位运算。移位运算也是深度1运算。因此,它需要与比较两个数字相同的时间。想想十进制系统,您可以通过在数字末尾添加零将任何数字乘以10、100、1000。任何优化编译器都会识别这种类型的m乘法运算并使用最有效的运算。现代CPU也相当先进,因此它们可以通过计算任何操作数中设置的位数来在硬件中执行相同的优化。如果只有一位,则运算将减少到移位
所以在你的例子中,乘以2就像比较两个数字一样快。正如上面提到的,任何优化编译器都会看到你在乘以两个常数,所以它会用返回一个常数来替换函数。正如其他人所解释的,这是因为Python的窥视孔优化器优化了算术运算但不是比较 在为一个基本编译器编写了我自己的窥视孔优化程序之后,我可以向您保证,优化常量比较和优化常量算术运算一样简单。因此,没有任何技术理由说明Python应该做后者而不是前者 然而,每一个这样的优化都必须单独编程,并且要付出两个代价:编程时间和额外的优化代码占用Python可执行文件中的空间。因此,您发现自己必须进行一些分类:这些优化中哪一个足够常见,从而使其值得付出代价
Python的实现者们似乎很合理地决定首先优化算术运算。也许他们会在未来的版本中进行比较。Wow,@mu的答案無 让我大吃一惊!然而,在得出结论时,重要的是不要一概而论……你正在检查常数的时间而不是变量的时间。对于变量,乘法似乎比比较慢 这里是一个更有趣的例子,在这个例子中,要比较的数字存储在实际变量中
import timeit
def go():
number=1000000000
print
print 'a>b, internal:',timeit.timeit(setup="a=1;b=1", stmt="a>b", number=number)
print 'a*b, internal:',timeit.timeit(setup="a=1;b=1", stmt="a*b", number=number)
print 'a>b, shell :',
%%timeit -n 1000000000 "a=1;b=1" "a>b"
print 'a*b, shell :',
%%timeit -n 1000000000 "a=1;b=1" "a*b"
go()
结果如下:
a>b, internal: 51.9467676445
a*b, internal: 63.870462403
a>b, shell :1000000000 loops, best of 3: 19.8 ns per loop
a>b, shell :1000000000 loops, best of 3: 19.9 ns per loop
宇宙秩序得以恢复;)
为了完整性,让我们看更多的例子…如果我们有一个变量和一个常数呢
import timeit
def go():
print 'a>2, shell :',
%%timeit -n 10000000 "a=42" "a>2"
print 'a*2, shell :',
%%timeit -n 10000000 "a=42" "a*2"
go()
a>2, shell :10000000 loops, best of 3: 18.3 ns per loop
a*2, shell :10000000 loops, best of 3: 19.3 ns per loop
胸部会怎么样
import timeit
def go():
print
number=1000000000
print 'a==b : ', timeit.timeit(setup="a=True;b=False",stmt="a==b",number=number)
print 'a and b : ', timeit.timeit(setup="a=True;b=False",stmt="a and b",number=number)
print 'boolean ==, shell :',
%%timeit -n 1000000000 "a=True;b=False" "a == b"
print 'boolean and, shell :',
%%timeit -n 1000000000 "a=False;b=False" "a and b"
go()
a==b : 70.8013108982
a and b : 38.0614485665
boolean ==, shell :1000000000 loops, best of 3: 17.7 ns per loop
boolean and, shell :1000000000 loops, best of 3: 16.4 ns per loop
:D这很有趣,它似乎是布尔值,比==快。不过,所有这些都可以作为t
import timeit
def go():
number=1000000 # change if you are in a hurry/ want to be more certain....
print '==== int ===='
print 'a>b : ', timeit.timeit(setup="a=1;b=2",stmt="a>b",number=number*100)
print 'a*b : ', timeit.timeit(setup="a=1;b=2",stmt="a*b",number=number*100)
setup = "import numpy as np;a=np.arange(0,100);b=np.arange(100,0,-1);"
print 'np: a>b : ', timeit.timeit(setup=setup,stmt="a>b",number=number)
print 'np: a*b : ', timeit.timeit(setup=setup,stmt="a*b",number=number)
print '==== float ===='
print 'float a>b : ', timeit.timeit(setup="a=1.1;b=2.3",stmt="a>b",number=number*100)
print 'float a*b : ', timeit.timeit(setup="a=1.1;b=2.3",stmt="a*b",number=number*100)
setup = "import numpy as np;a=np.arange(0,100,dtype=float);b=np.arange(100,0,-1,dtype=float);"
print 'np float a>b : ', timeit.timeit(setup=setup,stmt="a>b",number=number)
print 'np float a*b : ', timeit.timeit(setup=setup,stmt="a*b",number=number)
print '==== bool ===='
print 'a==b : ', timeit.timeit(setup="a=True;b=False",stmt="a==b",number=number*1000)
print 'a and b : ', timeit.timeit(setup="a=True;b=False",stmt="a and b",number=number*1000)
setup = "import numpy as np;a=np.arange(0,100)>50;b=np.arange(100,0,-1)>50;"
print 'np a == b : ', timeit.timeit(setup=setup,stmt="a == b",number=number)
print 'np a and b : ', timeit.timeit(setup=setup,stmt="np.logical_and(a,b)",number=number)
print 'np a == True : ', timeit.timeit(setup=setup,stmt="a == True",number=number)
print 'np a and True : ', timeit.timeit(setup=setup,stmt="np.logical_and(a,True)",number=number)
go()
==== int ====
a>b : 4.5121130192
a*b : 5.62955748632
np: a>b : 0.763992986986
np: a*b : 0.723006032235
==== float ====
float a>b : 6.39567713272
float a*b : 5.62149055215
np float a>b : 0.697037433398
np float a*b : 0.847941712765
==== bool ====
a==b : 6.91458288689
a and b : 3.6289697892
np a == b : 0.789666454087
np a and b : 0.724517620007
np a == True : 1.55066706189
np a and True : 1.44293071804