我还记得在汇编中,整数除法指令会返回商和余数。那么在Python中,使用内置的divmod()
函数相比于使用%
和//
运算符(假设需要同时获取商和余数)性能更好吗?
q, r = divmod(n, d)
q, r = (n // d, n % d)
我还记得在汇编中,整数除法指令会返回商和余数。那么在Python中,使用内置的divmod()
函数相比于使用%
和//
运算符(假设需要同时获取商和余数)性能更好吗?
q, r = divmod(n, d)
q, r = (n // d, n % d)
测量即了解(以下所有时间均基于 Macbook Pro 2.8Ghz i7):
>>> import sys, timeit
>>> sys.version_info
sys.version_info(major=2, minor=7, micro=12, releaselevel='final', serial=0)
>>> timeit.timeit('divmod(n, d)', 'n, d = 42, 7')
0.1473848819732666
>>> timeit.timeit('n // d, n % d', 'n, d = 42, 7')
0.10324406623840332
divmod()
函数在这里处于不利地位,因为每次需要查找全局变量。将其绑定到本地变量(timeit
时间试验中的所有变量都是本地变量)可以稍微提高性能:
>>> timeit.timeit('dm(n, d)', 'n, d = 42, 7; dm = divmod')
0.13460898399353027
但运算符仍然获胜,因为在执行函数调用 divmod()
时不必保留当前帧:
>>> import dis
>>> dis.dis(compile('divmod(n, d)', '', 'exec'))
1 0 LOAD_NAME 0 (divmod)
3 LOAD_NAME 1 (n)
6 LOAD_NAME 2 (d)
9 CALL_FUNCTION 2
12 POP_TOP
13 LOAD_CONST 0 (None)
16 RETURN_VALUE
>>> dis.dis(compile('(n // d, n % d)', '', 'exec'))
1 0 LOAD_NAME 0 (n)
3 LOAD_NAME 1 (d)
6 BINARY_FLOOR_DIVIDE
7 LOAD_NAME 0 (n)
10 LOAD_NAME 1 (d)
13 BINARY_MODULO
14 BUILD_TUPLE 2
17 POP_TOP
18 LOAD_CONST 0 (None)
21 RETURN_VALUE
//
和%
变体使用更多的操作码,但从性能上来看,CALL_FUNCTION
字节码比较棘手。
对于小整数,在PyPy中实际上没有太大区别;操作码的微小速度优势在C整数算术的高速性下消失得无影无踪:
>>>> import platform, sys, timeit
>>>> platform.python_implementation(), sys.version_info
('PyPy', (major=2, minor=7, micro=10, releaselevel='final', serial=42))
>>>> timeit.timeit('divmod(n, d)', 'n, d = 42, 7', number=10**9)
0.5659301280975342
>>>> timeit.timeit('n // d, n % d', 'n, d = 42, 7', number=10**9)
0.5471200942993164
我不得不把重复次数增加到10亿次才能展示出差异是多么微小,PyPy在这里的速度非常快。
然而,当数字变得较大时,divmod()
函数的速度则显著胜出:
>>>> timeit.timeit('divmod(n, d)', 'n, d = 2**74207281 - 1, 26', number=100)
17.620037078857422
>>>> timeit.timeit('n // d, n % d', 'n, d = 2**74207281 - 1, 26', number=100)
34.44323515892029
(现在,为了能在合理的时间内得到结果,我不得不将重复次数与霍布斯的数字相比降低一个数量级。)
这是因为PyPy不能再将那些整数作为C整数来解包;你可以看到使用sys.maxint
和sys.maxint + 1
之间的时间差异显著:
>>>> timeit.timeit('divmod(n, d)', 'import sys; n, d = sys.maxint, 26', number=10**7)
0.008622884750366211
>>>> timeit.timeit('n // d, n % d', 'import sys; n, d = sys.maxint, 26', number=10**7)
0.007693052291870117
>>>> timeit.timeit('divmod(n, d)', 'import sys; n, d = sys.maxint + 1, 26', number=10**7)
0.8396248817443848
>>>> timeit.timeit('n // d, n % d', 'import sys; n, d = sys.maxint + 1, 26', number=10**7)
1.0117690563201904
divmod
е®Ње…ЁеЏ–д»Јдє†//
е’Њ%
зљ„еЉџиѓЅе‘ўпјџ - Dimitris Fasarakis Hilliard&
掩码操作,因此通常的优化不再适用。您必须使用与int大小成正比的算法,并且在进行divmod时执行一次,而使用单个运算符执行两次会对其造成严重损害。 - Martijn Pietersdivmod(a, b)[::-1]
比a // b, a % b
更能表达意图吗?谢谢! - Ann Zen(a // b, a % b)
比 divmod(a, b)[::-1]
更能传达意图。我老实说,我不会记得 divmod
的输出顺序。当将输出分配给两个变量时,变量名通常可以清楚地表明它们的含义。 - nog642如果你使用“小型”原生整数,其中算术运算比函数调用快得多,那么Martijn的答案是正确的。然而,对于大整数,情况完全不同:
>>> import timeit
>>> timeit.timeit('divmod(n, d)', 'n, d = 2**74207281 - 1, 26', number=1000)
24.22666597366333
>>> timeit.timeit('n // d, n % d', 'n, d = 2**74207281 - 1, 26', number=1000)
49.517399072647095
当分割一个22百万位数字时,divmod几乎比单独执行除法和模数运算快两倍,正如您所预期的那样。
在我的机器上,交叉点大约在2 ^ 63左右,但不要相信我的话。正如Martijn所说,进行测量!当性能真正重要时,不要假设在一个地方成立的事情仍然会在另一个地方成立。
pulse_onset
来计算divmod
。pulse_onset.shape
(14307,)
使用这两个变量,divmod
更快。我猜测两次迭代数组比使用 divmod
更差,但我对内部机制不够熟悉。
t0 = time.time()
div_array = np.array([divmod(i, 256) for i in pulse_onset])
t1 = time.time()
total = t1-t0
print(total)
0.008155584335327148
t0 = time.time()
pulse_onset_int = np.array([round(i / 256) for i in pulse_onset])
remainder = np.array([i % 256 for i in pulse_onset])
t1 = time.time()
0.5383238792419434
做类似于这样的事情
np.array([(round(i / 256), i % 256) for i in pulse_onset])
稍微节省了一点时间(0.39537501335144043),所以可能不是大部分时间在数组上进行两次迭代,而是进行两次除法运算的时间更长。
timeit
来找出答案? - Martijn PietersCALL_FUNCTION
)可能会导致第一个版本执行较慢,但是使用timeit
可以确保。 - Łukasz Rogalski