Python是否自动用<<替换* 2 1?

时间:2015-06-11 11:25:31

标签: python python-3.x cpython python-internals

我看到了建议(参见例如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。

3 个答案:

答案 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