divmod() 是否比使用 % 和 // 运算符更快?
Is divmod() faster than using the % and // operators?
我记得在汇编中,整数除法指令同时产生商和余数。因此,在 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
(与 hobbs 的数字相比,我现在不得不将重复次数 降低 10 倍,只是为了在合理的时间内获得结果)。
这是因为 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
如果您使用 "small" 本机整数,Martijn 的回答是正确的,与函数调用相比,算术运算速度非常快。然而,对于 bigints,情况就完全不同了:
>>> 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
除以 2200 万位数字时,divmod 的速度几乎是分别进行除法和取模的两倍,正如您所料。
在我的机器上,交叉发生在 2^63 左右,但不要相信我的话。正如 Martijn 所说,测量!当性能真的很重要时,不要假设在一个地方适用的东西在另一个地方仍然适用。
我记得在汇编中,整数除法指令同时产生商和余数。因此,在 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
(与 hobbs 的数字相比,我现在不得不将重复次数 降低 10 倍,只是为了在合理的时间内获得结果)。
这是因为 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
如果您使用 "small" 本机整数,Martijn 的回答是正确的,与函数调用相比,算术运算速度非常快。然而,对于 bigints,情况就完全不同了:
>>> 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
除以 2200 万位数字时,divmod 的速度几乎是分别进行除法和取模的两倍,正如您所料。
在我的机器上,交叉发生在 2^63 左右,但不要相信我的话。正如 Martijn 所说,测量!当性能真的很重要时,不要假设在一个地方适用的东西在另一个地方仍然适用。