【问题标题】:Is divmod() faster than using the % and // operators?divmod() 是否比使用 % 和 // 运算符更快?
【发布时间】:2015-07-16 18:36:31
【问题描述】:

我记得在汇编中整数除法指令会产生商和余数。那么,在 python 中,内置的 divmod() 函数会比使用 %// 运算符更好(假设当然需要商和余数)?

q, r = divmod(n, d)
q, r = (n // d, n % d)

【问题讨论】:

  • 为什么不直接使用timeit 来查找?
  • 函数调用开销(字节码中的CALL_FUNCTION)可能会导致第一个版本的执行速度变慢,但timeit 是一种可以肯定的方法。
  • 哪个实现?

标签: python performance division modulo divmod


【解决方案1】:

测量就是知道(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

(与霍布斯的数字相比,我现在必须将重复次数调低 10 倍,以便在合理的时间内获得结果)。

这是因为 PyPy 不再能够将这些整数拆箱为 C 整数;您可以看到使用sys.maxintsys.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 完全摧毁了//% 大量的数字吗?
  • @JimFasarakis-Hilliard:大量支持。您不能再在 C 整数值上使用基本的 C & 掩码操作,因此通常的优化会消失。您必须使用与 int 的大小成正比的算法,并且在执行 divmod 时执行一次,而不是在单个运算符执行两次时执行此操作会造成严重伤害。
  • @AnnZen 您使用的是 PyPy 还是关键路径上的代码需要尽快执行?如果不是,请选择您认为能更好地传达意图的任何内容。
  • @MartijnPieters 代码不需要尽可能快......但我实际上需要反转坐标。 divmod(a, b)[::-1] 会比 a // b, a % b 更好地传达意图吗?谢谢!
  • @AnnZen 我会说不。 (a // b, a % b)divmod(a, b)[::-1] 更能传达意图。老实说,我不记得divmod 的输出顺序。将输出分配给两个变量时,变量名称通常会清楚。
【解决方案2】:

如果您使用“小”本机整数,Martijn 的答案是正确的,其中算术运算与函数调用相比非常快。但是,对于 bigint,情况就完全不同了:

>>> 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所说,测量!当性能真的很重要时,不要假设在一个地方适用的东西在另一个地方仍然适用。

【讨论】:

    猜你喜欢
    • 2013-07-23
    • 2014-11-07
    • 2012-06-18
    • 2015-02-06
    • 1970-01-01
    • 2011-06-16
    • 1970-01-01
    • 2019-02-14
    • 1970-01-01
    相关资源
    最近更新 更多