为什么2 * x * x比Python 3.x中的2 *(x * x)快,对于整数?

Why is 2 * x * x faster than 2 * ( x * x ) in Python 3.x, for integers?

Python整数倍频3.x下面以在线和1.77s:平均1.66s betweenP></

1
2
3
4
5
6
7
import time
start_time = time.time()
num = 0
for x in range(0, 10000000):
    # num += 2 * (x * x)
    num += 2 * x * x
print("--- %s seconds ---" % (time.time() - start_time))

如果你2 * x * xreplace with 2 *(x * x),EN和2.252.04之间。如何如何?P></

on the other is the opposite恩的手:在Java中,Java是2 * (x * x)更快。考试链接:为什么Java是*(2 * 2 *)*更快比在Java?P></

伊朗version of the program 10每个时代,这里是结果。P></

1
2
3
4
5
6
7
8
9
10
11
12
   2 * x * x        |   2 * (x * x)
---------------------------------------
1.7717654705047607  | 2.0789272785186768
1.735931396484375   | 2.1166207790374756
1.7093875408172607  | 2.024367570877075
1.7004504203796387  | 2.047525405883789
1.6676218509674072  | 2.254328966140747
1.699510097503662   | 2.0949244499206543
1.6889283657073975  | 2.0841963291168213
1.7243537902832031  | 2.1290600299835205
1.712965488433838   | 2.1942825317382812
1.7622807025909424  | 2.1200053691864014


首先,请注意我们在Python 2。x中没有看到相同的东西:

1
2
3
4
>>> timeit("for i in range(1000): 2*i*i")
51.00784397125244
>>> timeit("for i in range(1000): 2*(i*i)")
50.48330092430115

所以这让我们相信这是由于python 3中整数的变化:具体来说,python3在任何地方都使用EDOCX1(任意大的整数)。

对于足够小的整数(包括我们在这里考虑的整数),cpython实际上只使用O(mn)年级逐位乘法算法(对于较大的整数,它切换到karatsuba算法)。你可以在源代码中看到这个。

x*x中的位数大约是2*xx的两倍(因为log(x2)=2 log(x))。请注意,此上下文中的"数字"不是以10为基数的数字,而是一个30位的值(在cpython的实现中被视为单个数字)。因此,2是一个数字值,x2*x是循环所有迭代的一个数字值,但x*xx >= 2**15的两个数字。因此,对于x >= 2**152*x*x只需要一个一个数字的乘法,而2*(x*x)只需要一个一个一个数字的乘法和一个一个一个两个数字的乘法(因为x*x有2个30位数字)。

这里有一个直接的方法可以看到这个(python 3):

1
2
3
4
>>> timeit("a*b","a,b = 2, 123456**2", number=100000000)
5.796971936999967
>>> timeit("a*b","a,b = 2*123456, 123456", number=100000000)
4.3559221399999615

同样,将其与不到处使用任意长度整数的python 2进行比较:

1
2
3
4
>>> timeit("a*b","a,b = 2, 123456**2", number=100000000)
3.0912468433380127
>>> timeit("a*b","a,b = 2*123456, 123456", number=100000000)
3.1120400428771973

(一个有趣的注意事项:如果你看一下源代码,你会发现算法实际上有一个特殊的平方数案例(我们在这里做的),但即使这样也不足以克服2*(x*x)只需要处理更多的数字的事实。)


整数的python intern表示是特殊的,它使用30位的槽:

1
2
3
4
5
In [6]: sys.getsizeof(2**30-1)
Out[6]: 28 # one slot + heading

In [7]: sys.getsizeof(2**30)
Out[7]: 32 # two slots

所以,一切都发生在基本B = 2**30 = 1 073 741 824 ~1 billion中,就好像python计数一样。

对于想要计算2*4*4的人,有两种方法:

  • (2*4)*4=8*4=32=30+2如果知道添加表,则立即添加。
  • 2*(4*4)=2*16=2*10+2*6=(2*10+10)+2=30+2,因为我们必须停止操作。

Python也有同样的问题。如果x是一个数而不是2x < B < x2,就让x2 = aB+ba,b 一起。x2存储在2个插槽中,我注意到(a|b)。计算导致(此处不管理进位):

1
2
   (x*x)*2 =>  (a|b)*2 => (2*a|2*b)
   (2*x)*x =>  (2x)*x =>(2a|2b)

在第一种情况下,EDCOX1×7操作是两次,而在第一种情况下只有一次。这就解释了差异。


如果您的基准测试是正确的(没有检查),可能是因为python整数可能是两种不同的东西:小的时候是本机整数(计算速度快),大的时候是大的整数(计算速度慢)。第一个语法在第一个操作之后保持较小的大小,而第二个语法可能导致两个涉及大整数的操作。


据我所知,在使用2 * (x * x)的版本中,内存访问稍微多一点。我打印了反汇编的字节码,似乎证明了:

2 * x * x相关部分:

1
2
3
4
5
6
7
8
9
7          28 LOAD_FAST                1 (num)
           30 LOAD_CONST               3 (2)
           32 LOAD_FAST                2 (x)
           34 BINARY_MULTIPLY
           36 LOAD_FAST                2 (x)
           38 BINARY_MULTIPLY
           40 INPLACE_ADD
           42 STORE_FAST               1 (num)
           44 JUMP_ABSOLUTE           24

2 * (x * x)相关部分:

1
2
3
4
5
6
7
8
9
  7          28 LOAD_FAST                1 (num)
             30 LOAD_CONST               3 (2)
             32 LOAD_FAST                2 (x)
             34 LOAD_FAST                2 (x)
             36 BINARY_MULTIPLY                 <=== 1st multiply x*x in a temp value
             38 BINARY_MULTIPLY                 <=== then multiply result with 2
             40 INPLACE_ADD
             42 STORE_FAST               1 (num)
             44 JUMP_ABSOLUTE           24