J'essayais de mettre en œuvre un test de primalité de Miller-Rabin et j'étais perplexe sur la raison pour laquelle cela prenait si longtemps (> 20 secondes) pour les nombres de taille moyenne (~ 7 chiffres). J'ai finalement trouvé que la ligne de code suivante était la source du problème:
x = a**d % n
(où a
, d
et n
sont tous similaires, mais inégaux, des nombres de taille moyenne, **
est l'opérateur d'exponentiation et %
est l'opérateur modulo)
J'ai ensuite essayé de le remplacer par ce qui suit:
x = pow(a, d, n)
et c'est par comparaison presque instantané.
Pour le contexte, voici la fonction d'origine:
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 exemple de calcul chronométré:
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)})
Sortie (exécutée avec PyPy 1.9.0):
2642565
time: 23.785543s
2642565
time: 0.000030s
Sortie (exécutée avec Python 3.3.0, 2.7.2 renvoie des temps très similaires):
2642565
time: 14.426975s
2642565
time: 0.000021s
Et une question connexe, pourquoi ce calcul est-il presque deux fois plus rapide lorsqu'il est exécuté avec Python 2 ou 3 qu'avec PyPy, alors que PyPy est généralement beaucoup plus rapide ?
la source
>>> 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
type natif , mais pas nécessairement avec d'autres types intégraux. Mais dans les anciennes versions, il y avait des règles sur l'ajustement dans un Clong
, la forme à trois arguments était autoriséefloat
, etc. (J'espère que vous n'utilisez pas la version 2.1 ou antérieure, et que vous n'utilisez pas de types intégraux personnalisés des modules C, donc aucun de cela compte pour vous.)x ** y % n
,x
pourrait être un objet qui implémente__pow__
et, basé sur un nombre aléatoire, renvoie l'un des différents objets implémentés__mod__
d'une manière qui dépend également de nombres aléatoires, etc..3 ** .4 % .5
c'est parfaitement légal, mais si le compilateur le transformait, cela lèveraitpow(.3, .4, .5)
unTypeError
. Le compilateur devrait être en mesure de savoir quea
,d
etn
sont garantis être les valeurs d'un type intégral (ou peut - être juste en particulier de typeint
, parce que la transformation ne permet pas autrement), etd
est garanti à être négatif. C'est quelque chose qu'un JIT pourrait faire, mais un compilateur statique pour un langage avec des types dynamiques et aucune inférence ne peut tout simplement pas.BrenBarn a répondu à votre question principale. Pour votre côté:
Si vous lisez la page de performances de PyPy , c'est exactement le genre de chose pour laquelle PyPy n'est pas bon - en fait, le tout premier exemple qu'ils donnent:
Théoriquement, transformer une énorme exponentiation suivie d'un mod en une exponentiation modulaire (au moins après la première passe) est une transformation qu'un JIT pourrait être capable de faire… mais pas le JIT de PyPy.
En remarque, si vous avez besoin de faire des calculs avec d'énormes entiers, vous voudrez peut-être regarder des modules tiers comme
gmpy
, qui peuvent parfois être beaucoup plus rapides que l'implémentation native de CPython dans certains cas en dehors des utilisations traditionnelles, et qui en ont également beaucoup de fonctionnalités supplémentaires que vous auriez autrement à écrire vous-même, au prix d'être moins pratique.la source
gmpy
est également plus lent au lieu de plus rapide dans quelques cas, et rend beaucoup de choses simples moins pratiques. Ce n'est pas toujours la réponse - mais c'est parfois le cas. Cela vaut donc la peine de regarder si vous avez affaire à d'énormes entiers et que le type natif de Python ne semble pas assez rapide.Il existe des raccourcis pour faire l'exponentiation modulaire: par exemple, vous pouvez trouver
a**(2i) mod n
pour chaquei
de1
àlog(d)
et multiplier ensemble (modn
) les résultats intermédiaires dont vous avez besoin. Une fonction d'exponentiation modulaire dédiée telle que 3 argumentspow()
peut tirer parti de ces astuces car elle sait que vous faites de l'arithmétique modulaire. L'analyseur Python ne peut pas reconnaître cela étant donné l'expression nuea**d % n
, il effectuera donc le calcul complet (ce qui prendra beaucoup plus de temps).la source
Le moyen de
x = a**d % n
calculer est de montera
à lad
puissance, puis modulo cela avecn
. Premièrement, sia
est grand, cela crée un nombre énorme qui est ensuite tronqué. Cependant,x = pow(a, d, n)
est très probablement optimisé pour que seuls les derniersn
chiffres soient suivis, qui sont tout ce qui est nécessaire pour calculer la multiplication modulo un nombre.la source
**
et pourpow
.