我看到了建议(参见例如Is multiplication and division using shift operators in C actually faster?),你不应该手动用乘法运算符替换乘法,因为编译器必须自动执行,而移位运算符会降低可读性。我写了一个简单的测试来检查这个:
import numpy as np
import time
array1 = np.random.randint(size=10 ** 6, low=0, high=10 ** 5)
array2 = np.zeros((10 ** 6,), dtype=np.int)
total = 0.0
for i in range(100):
start = time.clock()
for j in range(len(array2)):
array2[j] = array1[j] * 2
total += time.clock() - start
print("*2 time = " + str(round(total / 10, 5)) + " ms")
total = 0.0
for i in range(100):
start = time.clock()
for j in range(len(array2)):
array2[j] = array1[j] << 1
total += time.clock() - start
print("<< 1 time = " + str(round(total / 10, 5)) + " ms")
total = 0.0
for i in range(100):
start = time.clock()
for j in range(len(array2)):
array2[j] = array1[j] // 2
total += time.clock() - start
print("//2 time = " + str(round(total / 10, 5)) + " ms")
total = 0.0
for i in range(100):
start = time.clock()
for j in range(len(array2)):
array2[j] = array1[j] >> 1
total += time.clock() - start
print(">> 1 time = " + str(round(total / 10, 5)) + " ms")
我使用了等效的操作(* 2
相当于<< 1
和// 2
相当于>> 1
),结果如下:
*2 time = 5.15086 ms
<< 1 time = 4.76214 ms
//2 time = 5.17429 ms
>> 1 time = 4.79294 ms
有什么问题?我的测试方法有误吗?时间测量是错误的吗?或者Python不执行这样的优化(如果是的话,我应该害怕吗?)我在Win 8.1 x64上使用了cPython 3.4.2 x64。
答案 0 :(得分:6)
此优化不会发生在字节码级别:
>>> import dis
>>> dis.dis(lambda x: x*2)
1 0 LOAD_FAST 0 (x)
3 LOAD_CONST 1 (2)
6 BINARY_MULTIPLY
7 RETURN_VALUE
>>> dis.dis(lambda x: x<<1)
1 0 LOAD_FAST 0 (x)
3 LOAD_CONST 1 (1)
6 BINARY_LSHIFT
7 RETURN_VALUE
dis模块允许您在执行代码时向您展示Python内部发生的事情,或者更准确地说,确切地说是执行了什么。输出显示*
运算符已映射到BINARY_MULTIPLY
,<<
运算符已映射到BINARY_LSHIFT
。这两个字节码操作在C。
答案 1 :(得分:4)
使用dis
(查看函数的字节码等价物)和timeit
(比使用time
手动执行更健壮的时间)可以让您更好地了解什么是在内部进行。测试脚本:
def multiply(x):
return x * 2
def l_shift(x):
return x << 1
def divide(x):
return x // 2
def r_shift(x):
return x >> 1
if __name__ == '__main__':
import dis
import timeit
methods = (multiply, l_shift, divide, r_shift)
setup = 'from __main__ import {}'.format(
', '.join(method.__name__ for method in methods),
)
for method in methods:
print method.__name__
dis.dis(method)
print timeit.timeit(
'for x in range(10): {}(x)'.format(method.__name__),
setup=setup,
)
print
输出(Windows 7上的CPython v2.7.6):
multiply
2 0 LOAD_FAST 0 (x)
3 LOAD_CONST 1 (2)
6 BINARY_MULTIPLY
7 RETURN_VALUE
2.22467834797
l_shift
5 0 LOAD_FAST 0 (x)
3 LOAD_CONST 1 (1)
6 BINARY_LSHIFT
7 RETURN_VALUE
2.05381004158
divide
8 0 LOAD_FAST 0 (x)
3 LOAD_CONST 1 (2)
6 BINARY_FLOOR_DIVIDE
7 RETURN_VALUE
2.43717730095
r_shift
11 0 LOAD_FAST 0 (x)
3 LOAD_CONST 1 (1)
6 BINARY_RSHIFT
7 RETURN_VALUE
2.08359396854
显然,Python 不用相等的位移代替乘法/除法运算(例如BINARY_FLOOR_DIVIDE
不会被BINARY_RSHIFT
替换),尽管它看起来像这样的优化可以提供性能改进。至于为什么比特移位更快,请参阅例如关于程序员的Speeds of << >> multiplication and division。
答案 2 :(得分:4)
只有在非常有限的情况下,CPython才能实现这些优化。原因是CPython是一种躲避式的语言。
鉴于代码片段x * 2
,这可能意味着取决于x
的值的非常不同的东西。如果x
是一个整数,那么它的确含义与x << 1
相同。但是,如果x
是浮点数或字符串或列表或以其自己独特的方式实现__mul__
的任何其他类,那么它肯定与{{1}具有相同的含义}}。例如,x << 1
。因此,除非在编译时知道"a" * 2 == "aa"
的值,否则无法进行此优化。如果预先知道x
的值,则可以优化整个操作,例如。
x
您可以看到编译器已经执行了操作本身,只返回常量值In [3]: import dis
In [4]: def f():
...: return 2 * 2
...:
In [5]: dis.dis(f)
2 0 LOAD_CONST 2 (4)
3 RETURN_VALUE
。