Miller-Rabin primality test を実装しようとしていましたが、中規模の数値(〜7桁)でこれほど長い時間(> 20秒)かかっていた理由に戸惑いました。最終的に、次のコード行が問題の原因であることがわかりました。
x = a**d % n
(ここで、a
、d
、および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倍速くなります。
modular exponentiation に関するWikipediaの記事を参照してください。基本的に、a**d % n
、実際に計算する必要があるa**d
、これはかなり大きい可能性があります。しかし、計算方法はありますa**d % n
を計算することなくa**d
自体、そしてそれがpow
が行うことです。 **
演算子は、これを行うことができません。これは、モジュラスをすぐに取得することを「将来を見る」ことができないためです。
BrenBarnが主な質問に答えました。あなたのために:
Python 2または3で実行した場合、通常PyPyがはるかに高速であるのに、PyPyよりもほぼ2倍速いのはなぜですか?
PyPyの パフォーマンスページ を読んだ場合、これはまさにPyPyが得意としないようなものです。実際、最初の例は次のとおりです。
悪い例としては、長いlongで計算を行うことが挙げられます。これは、最適化できないサポートコードによって実行されます。
理論的には、MODが続く巨大なべき乗をモジュラーべき乗に変換することは(少なくとも最初のパスの後)、JITが行うことのできる変換ですが、PyPyのJITはできません。
サイドノートとして、巨大な整数で計算を行う必要がある場合は、 gmpy
のようなサードパーティのモジュールをご覧ください。これはCPythonのネイティブ実装よりもはるかに高速です。場合によっては、主流以外の用途で使用されます。また、利便性を犠牲にして、それ以外の場合は自分で記述する必要がある多くの追加機能があります。
モジュラーべき乗を行うショートカットがあります。たとえば、1
からa**(2i) mod n
までのi
ごとにlog(d)
を見つけて、中間結果を乗算(mod n
)できますあなたが必要です。 3-argument pow()
のような専用のモジュラー指数関数は、モジュラー演算を実行していることを知っているため、このようなトリックを活用できます。 Pythonパーサーは、むき出しの式a**d % n
を指定するとこれを認識できないため、完全な計算を実行します(これにはかなり時間がかかります)。
_x = a**d % n
_の計算方法は、a
をd
乗し、それをn
でモジュロすることです。第一に、a
が大きい場合、巨大な数が作成され、その後切り捨てられます。ただし、x = pow(a, d, n)
は最適化される可能性が最も高いため、最後のn
桁のみが追跡され、これらはすべてモジュロ乗算の計算に必要です。