整数除算命令は商と剰余の両方を生成することをアセンブリから覚えています。したがって、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はここで非常に高速です)。
ただし、数値が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
_
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をそのように受け取らないでください。マルティンが言うように、測定してください!パフォーマンスが本当に重要な場合、ある場所で真実であったことが別の場所でも真実であるとは限りません。