web-dev-qa-db-ja.com

なぜpow(a、d、n)はa ** d%nよりもずっと速いのですか?

Miller-Rabin primality test を実装しようとしていましたが、中規模の数値(〜7桁)でこれほど長い時間(> 20秒)かかっていた理由に戸惑いました。最終的に、次のコード行が問題の原因であることがわかりました。

x = a**d % n

(ここで、ad、およびnはすべて同様ですが、等しくない、中規模の数値、**はべき乗演算子であり、%はモジュロ演算子です)

それから私はそれを次のものに置き換えようとしました:

x = pow(a, d, n)

それに比べて、ほとんど瞬時です。

コンテキストについては、元の関数は次のとおりです。

from random import randint

def primalityTest(n, k):
    if n < 2:
        return False
    if n % 2 == 0:
        return False
    s = 0
    d = n - 1
    while d % 2 == 0:
        s += 1
        d >>= 1
    for i in range(k):
        Rand = randint(2, n - 2)
        x = Rand**d % n         # offending line
        if x == 1 or x == n - 1:
            continue
        for r in range(s):
            toReturn = True
            x = pow(x, 2, n)
            if x == 1:
                return False
            if x == n - 1:
                toReturn = False
                break
        if toReturn:
            return False
    return True

print(primalityTest(2700643,1))

タイミング計算の例:

from timeit import timeit

a = 2505626
d = 1520321
n = 2700643

def testA():
    print(a**d % n)

def testB():
    print(pow(a, d, n))

print("time: %(time)fs" % {"time":timeit("testA()", setup="from __main__ import testA", number=1)})
print("time: %(time)fs" % {"time":timeit("testB()", setup="from __main__ import testB", number=1)})

出力(PyPy 1.9.0で実行):

2642565
time: 23.785543s
2642565
time: 0.000030s

出力(Python 3.3.0、2.7.2で非常によく似た時間を返します):

2642565
time: 14.426975s
2642565
time: 0.000021s

また、関連する質問です。通常、PyPyが はるかに速い の場合、Python 2または3でPyPyを使用した場合、この計算はほぼ2倍速くなります。

107
lyallcooper

modular exponentiation に関するWikipediaの記事を参照してください。基本的に、a**d % n、実際に計算する必要があるa**d、これはかなり大きい可能性があります。しかし、計算方法はありますa**d % nを計算することなくa**d自体、そしてそれがpowが行うことです。 **演算子は、これを行うことができません。これは、モジュラスをすぐに取得することを「将来を見る」ことができないためです。

160
BrenBarn

BrenBarnが主な質問に答えました。あなたのために:

Python 2または3で実行した場合、通常PyPyがはるかに高速であるのに、PyPyよりもほぼ2倍速いのはなぜですか?

PyPyの パフォーマンスページ を読んだ場合、これはまさにPyPyが得意としないようなものです。実際、最初の例は次のとおりです。

悪い例としては、長いlongで計算を行うことが挙げられます。これは、最適化できないサポートコードによって実行されます。

理論的には、MODが続く巨大なべき乗をモジュラーべき乗に変換することは(少なくとも最初のパスの後)、JITが行うことのできる変換ですが、PyPyのJITはできません。

サイドノートとして、巨大な整数で計算を行う必要がある場合は、 gmpy のようなサードパーティのモジュールをご覧ください。これはCPythonのネイティブ実装よりもはるかに高速です。場合によっては、主流以外の用途で使用されます。また、利便性を犠牲にして、それ以外の場合は自分で記述する必要がある多くの追加機能があります。

37
abarnert

モジュラーべき乗を行うショートカットがあります。たとえば、1からa**(2i) mod nまでのiごとにlog(d)を見つけて、中間結果を乗算(mod n)できますあなたが必要です。 3-argument pow()のような専用のモジュラー指数関数は、モジュラー演算を実行していることを知っているため、このようなトリックを活用できます。 Pythonパーサーは、むき出しの式a**d % nを指定するとこれを認識できないため、完全な計算を実行します(これにはかなり時間がかかります)。

11
atomicinf

_x = a**d % n_の計算方法は、ad乗し、それをnでモジュロすることです。第一に、aが大きい場合、巨大な数が作成され、その後切り捨てられます。ただし、x = pow(a, d, n)は最適化される可能性が最も高いため、最後のn桁のみが追跡され、これらはすべてモジュロ乗算の計算に必要です。

3
Yuushi