web-dev-qa-db-ja.com

divmod()は%および//演算子を使用するよりも高速ですか?

整数除算命令は商と剰余の両方を生成することをアセンブリから覚えています。したがって、pythonでは、組み込みのdivmod()関数は、%演算子と//演算子を使用するよりもパフォーマンスが優れていますか?

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

測定することは知ることです(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はここで非常に高速です)。

ただし、数値がlargeの場合、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
_

(私は今downホブの数と比較して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
_
43
Martijn Pieters

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倍の速度です。

私のマシンでは、クロスオーバーは2 ^ 63あたりのどこかで発生しますが、私のWordをそのように受け取らないでください。マルティンが言うように、測定してください!パフォーマンスが本当に重要な場合、ある場所で真実であったことが別の場所でも真実であるとは限りません。

16
hobbs