Python 是否会自动将 * 2 替换为 << 1?
Does Python automatically replace * 2 with << 1?
我看到一些建议(请参见 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。
此优化不发生在字节码级别:
>>> 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 模块允许您向您展示代码执行时 "inside" Python 发生了什么,或者更准确地说,执行了什么。输出显示 *
运算符映射到 BINARY_MULTIPLY
,<<
运算符映射到 BINARY_LSHIFT
。这两个字节码操作是用C实现的。
使用 dis
(to look at the bytecode equivalent of functions) and 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 是 而不是 将 multiplication/division 操作替换为等效的移位(例如 BINARY_FLOOR_DIVIDE
未替换为 BINARY_RSHIFT
),尽管看起来这样的优化 可以 提高性能。至于 为什么 位移更快,请参见例如Speeds of << >> multiplication and division 关于程序员。
只有在非常有限的情况下,CPython 才能实现这些优化。原因是 CPython 是一种 ducked 类型的语言。
鉴于代码片段 x * 2
,这可能意味着非常不同的事情,具体取决于 x
的值。如果 x
是一个整数,那么它确实与 x << 1
具有相同的含义。但是,如果 x
是一个浮点数或字符串或列表或任何其他以其独特的方式实现 __mul__
的 class 那么它肯定与 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
可以看到编译器自己进行了运算,只是returns常量值4
.
我看到一些建议(请参见 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。
此优化不发生在字节码级别:
>>> 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 模块允许您向您展示代码执行时 "inside" Python 发生了什么,或者更准确地说,执行了什么。输出显示 *
运算符映射到 BINARY_MULTIPLY
,<<
运算符映射到 BINARY_LSHIFT
。这两个字节码操作是用C实现的。
使用 dis
(to look at the bytecode equivalent of functions) and 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 是 而不是 将 multiplication/division 操作替换为等效的移位(例如 BINARY_FLOOR_DIVIDE
未替换为 BINARY_RSHIFT
),尽管看起来这样的优化 可以 提高性能。至于 为什么 位移更快,请参见例如Speeds of << >> multiplication and division 关于程序员。
只有在非常有限的情况下,CPython 才能实现这些优化。原因是 CPython 是一种 ducked 类型的语言。
鉴于代码片段 x * 2
,这可能意味着非常不同的事情,具体取决于 x
的值。如果 x
是一个整数,那么它确实与 x << 1
具有相同的含义。但是,如果 x
是一个浮点数或字符串或列表或任何其他以其独特的方式实现 __mul__
的 class 那么它肯定与 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
可以看到编译器自己进行了运算,只是returns常量值4
.