Estaba tratando de implementar una prueba de primalidad Miller-Rabin y me sorprendió por qué tardaba tanto (> 20 segundos) en números medianos (~ 7 dígitos). Finalmente encontré que la siguiente línea de código es la fuente del problema:
x = a**d % n
(donde a
,, d
y n
son todos similares, pero desiguales, números medianos, **
es el operador de exponenciación y %
es el operador de módulo)
Luego intenté reemplazarlo con lo siguiente:
x = pow(a, d, n)
y en comparación es casi instantáneo.
Para el contexto, aquí está la función original:
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))
Un ejemplo de cálculo cronometrado:
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)})
Salida (ejecutar con PyPy 1.9.0):
2642565
time: 23.785543s
2642565
time: 0.000030s
Salida (ejecutar con Python 3.3.0, 2.7.2 devuelve tiempos muy similares):
2642565
time: 14.426975s
2642565
time: 0.000021s
Y una pregunta relacionada, ¿por qué este cálculo es casi dos veces más rápido cuando se ejecuta con Python 2 o 3 que con PyPy, cuando normalmente PyPy es mucho más rápido ?
fuente
>>> print pow.__doc__ pow(x, y[, z]) -> number With two arguments, equivalent to x**y. With three arguments, equivalent to (x**y) % z, but may be more efficient (e.g. for longs).
int
tipo nativo , pero no necesariamente con otros tipos integrales. Pero en versiones anteriores había reglas sobre cómo encajar en una Clong
, se permitía la forma de tres argumentosfloat
, etc. (Con suerte, no está usando 2.1 o anterior, y no está usando ningún tipo integral personalizado de módulos C, por lo que ninguno de esto es importante para usted.)x ** y % n
,x
podría ser un objeto que implementa__pow__
y, en base a un número aleatorio, devuelve uno de varios objetos diferentes de aplicación__mod__
de manera que también dependen de números aleatorios, etc..3 ** .4 % .5
es perfectamente legal, pero si el compilador lo transforma enpow(.3, .4, .5)
eso generaría unTypeError
. El compilador debería poder saber esoa
,d
yn
se garantiza que son valores de un tipo integral (o tal vez solo específicamente de tipoint
, porque la transformación no ayuda de otra manera), yd
se garantiza que no serán negativos. Eso es algo que un JIT podría hacer, pero un compilador estático para un lenguaje con tipos dinámicos y sin inferencia simplemente no puede.