Il y a quelques petites optimisations pour votre version. En inversant les rôles de True et False, vous pouvez changer " if flags[i] is False:
" à " if flags[i]:
". Et la valeur de départ pour le deuxième range
La déclaration peut être i*i
au lieu de i*3
. Votre version originale prend 0,166 secondes sur mon système. Avec ces changements, la version ci-dessous prend 0,156 secondes sur mon système.
def prime_numbers(limit=1000000):
'''Prime number generator. Yields the series
2, 3, 5, 7, 11, 13, 17, 19, 23, 29 ...
using Sieve of Eratosthenes.
'''
yield 2
sub_limit = int(limit**0.5)
flags = [True, True] + [False] * (limit - 2)
# Step through all the odd numbers
for i in range(3, limit, 2):
if flags[i]:
continue
yield i
# Exclude further multiples of the current prime number
if i <= sub_limit:
for j in range(i*i, limit, i<<1):
flags[j] = True
Cela n'aide pas votre problème de mémoire, cependant.
Pour entrer dans le monde des extensions C, j'ai utilisé la version de développement de gmpy . (La version de développement s'appelle gmpy2 et supporte les entiers mutables appelés xmpz. En utilisant gmpy2 et le code suivant, j'ai un temps d'exécution de 0,140 secondes. Le temps d'exécution pour une limite de 1.000.000.000 est de 158 secondes.
import gmpy2
def prime_numbers(limit=1000000):
'''Prime number generator. Yields the series
2, 3, 5, 7, 11, 13, 17, 19, 23, 29 ...
using Sieve of Eratosthenes.
'''
yield 2
sub_limit = int(limit**0.5)
# Actual number is 2*bit_position + 1.
oddnums = gmpy2.xmpz(1)
current = 0
while True:
current += 1
current = oddnums.bit_scan0(current)
prime = 2 * current + 1
if prime > limit:
break
yield prime
# Exclude further multiples of the current prime number
if prime <= sub_limit:
for j in range(2*current*(current+1), limit>>1, prime):
oddnums.bit_set(j)
En poussant les optimisations, et en sacrifiant la clarté, j'obtiens des temps d'exécution de 0,107 et 123 secondes avec le code suivant :
import gmpy2
def prime_numbers(limit=1000000):
'''Prime number generator. Yields the series
2, 3, 5, 7, 11, 13, 17, 19, 23, 29 ...
using Sieve of Eratosthenes.
'''
yield 2
sub_limit = int(limit**0.5)
# Actual number is 2*bit_position + 1.
oddnums = gmpy2.xmpz(1)
f_set = oddnums.bit_set
f_scan0 = oddnums.bit_scan0
current = 0
while True:
current += 1
current = f_scan0(current)
prime = 2 * current + 1
if prime > limit:
break
yield prime
# Exclude further multiples of the current prime number
if prime <= sub_limit:
list(map(f_set,range(2*current*(current+1), limit>>1, prime)))
Edit : Sur la base de cet exercice, j'ai modifié gmpy2 pour accepter xmpz.bit_set(iterator)
. En utilisant le code suivant, le temps d'exécution pour tous les nombres premiers inférieurs à 1 000 000 000 est de 56 secondes pour Python 2.7 et de 74 secondes pour Python 3.2. (Comme indiqué dans les commentaires, xrange
est plus rapide que range
.)
import gmpy2
try:
range = xrange
except NameError:
pass
def prime_numbers(limit=1000000):
'''Prime number generator. Yields the series
2, 3, 5, 7, 11, 13, 17, 19, 23, 29 ...
using Sieve of Eratosthenes.
'''
yield 2
sub_limit = int(limit**0.5)
oddnums = gmpy2.xmpz(1)
f_scan0 = oddnums.bit_scan0
current = 0
while True:
current += 1
current = f_scan0(current)
prime = 2 * current + 1
if prime > limit:
break
yield prime
if prime <= sub_limit:
oddnums.bit_set(iter(range(2*current*(current+1), limit>>1, prime)))
Edit #2 : Encore un essai ! J'ai modifié gmpy2 pour accepter xmpz.bit_set(slice)
. En utilisant le code suivant, le temps d'exécution pour tous les nombres premiers inférieurs à 1 000 000 000 est d'environ 40 secondes pour Python 2.7 et Python 3.2.
from __future__ import print_function
import time
import gmpy2
def prime_numbers(limit=1000000):
'''Prime number generator. Yields the series
2, 3, 5, 7, 11, 13, 17, 19, 23, 29 ...
using Sieve of Eratosthenes.
'''
yield 2
sub_limit = int(limit**0.5)
flags = gmpy2.xmpz(1)
# pre-allocate the total length
flags.bit_set((limit>>1)+1)
f_scan0 = flags.bit_scan0
current = 0
while True:
current += 1
current = f_scan0(current)
prime = 2 * current + 1
if prime > limit:
break
yield prime
if prime <= sub_limit:
flags.bit_set(slice(2*current*(current+1), limit>>1, prime))
start = time.time()
result = list(prime_numbers(1000000000))
print(time.time() - start)
Edit #3 : J'ai mis à jour gmpy2 pour supporter correctement le découpage au niveau du bit d'un xmpz. Pas de changement dans les performances mais une API bien plus agréable. J'ai fait quelques ajustements et j'ai réussi à réduire le temps à environ 37 secondes. (Voir Edit #4 pour les changements dans gmpy2 2.0.0b1).
from __future__ import print_function
import time
import gmpy2
def prime_numbers(limit=1000000):
'''Prime number generator. Yields the series
2, 3, 5, 7, 11, 13, 17, 19, 23, 29 ...
using Sieve of Eratosthenes.
'''
sub_limit = int(limit**0.5)
flags = gmpy2.xmpz(1)
flags[(limit>>1)+1] = True
f_scan0 = flags.bit_scan0
current = 0
prime = 2
while prime <= sub_limit:
yield prime
current += 1
current = f_scan0(current)
prime = 2 * current + 1
flags[2*current*(current+1):limit>>1:prime] = True
while prime <= limit:
yield prime
current += 1
current = f_scan0(current)
prime = 2 * current + 1
start = time.time()
result = list(prime_numbers(1000000000))
print(time.time() - start)
Edit #4 : J'ai fait quelques changements dans gmpy2 2.0.0b1 qui cassent l'exemple précédent. gmpy2 ne traite plus True comme une valeur spéciale qui fournit une source infinie de 1-bits. -1 devrait être utilisé à la place.
from __future__ import print_function
import time
import gmpy2
def prime_numbers(limit=1000000):
'''Prime number generator. Yields the series
2, 3, 5, 7, 11, 13, 17, 19, 23, 29 ...
using Sieve of Eratosthenes.
'''
sub_limit = int(limit**0.5)
flags = gmpy2.xmpz(1)
flags[(limit>>1)+1] = 1
f_scan0 = flags.bit_scan0
current = 0
prime = 2
while prime <= sub_limit:
yield prime
current += 1
current = f_scan0(current)
prime = 2 * current + 1
flags[2*current*(current+1):limit>>1:prime] = -1
while prime <= limit:
yield prime
current += 1
current = f_scan0(current)
prime = 2 * current + 1
start = time.time()
result = list(prime_numbers(1000000000))
print(time.time() - start)
Edit #5 : J'ai apporté quelques améliorations à gmpy2 2.0.0b2. Vous pouvez maintenant itérer sur tous les bits qui sont soit activés soit désactivés. Le temps d'exécution a été amélioré de ~30%.
from __future__ import print_function
import time
import gmpy2
def sieve(limit=1000000):
'''Returns a generator that yields the prime numbers up to limit.'''
# Increment by 1 to account for the fact that slices do not include
# the last index value but we do want to include the last value for
# calculating a list of primes.
sieve_limit = gmpy2.isqrt(limit) + 1
limit += 1
# Mark bit positions 0 and 1 as not prime.
bitmap = gmpy2.xmpz(3)
# Process 2 separately. This allows us to use p+p for the step size
# when sieving the remaining primes.
bitmap[4 : limit : 2] = -1
# Sieve the remaining primes.
for p in bitmap.iter_clear(3, sieve_limit):
bitmap[p*p : limit : p+p] = -1
return bitmap.iter_clear(2, limit)
if __name__ == "__main__":
start = time.time()
result = list(sieve(1000000000))
print(time.time() - start)
print(len(result))
0 votes
Petit détail : il faut compter 4 ou 8 octets par booléen (selon que vous êtes sur un système 32 ou 64 bits), plutôt que 1 : en interne, la liste
flags
est juste un tableau C de pointeurs (PyObject *).0 votes
Merci pour la correction.
:]
0 votes
Vous pourriez utiliser
numpy
dans Python 2.x rosettacode.org/wiki/Sieve_of_Eratosthenes#Using_numpy Il est beaucoup plus rapide (~20 fois).0 votes
Il faut ~28 secondes pour générer jusqu'à la limite 1e9 en utilisant primes_upto2(), primes_upto3() du lien ci-dessus. Version C++ ~14 secondes, version C ~13 secondes.
0 votes
@J.F. Sebastian : Beau lien. Je vais faire un essai après le travail aujourd'hui, mais notez qu'il n'utilise pas de générateur, et qu'il ne renvoie qu'un tableau de zéros et de uns. Pas vraiment complet.
:]
0 votes
Xavier Ho :
numpy.nonzero()
renvoie à indices du réseau de tamisage qui sont des primes dans ce cas. Vous auriez dû l'essayer.0 votes
De l'affaire
gmpy
-est légèrement plus rapide que la versioniprimes_upto()
mais il est nettement plus lent que lenumpy
-pour les versions basées sur la technologie. Pour 1e6 : iprimes_jusqu'à : 0.39, primes_upto2 : 0.02, primes_upto3 : 0.02, prime_numbers : 0.29. Pour 1e9prime_numbers
est trop lent (mais il n'y a pas d'erreur de mémoire). primes_upto2 : 28.96, primes_upto3 : 27.55, prime_numbers : 509.74 (temps en secondes).0 votes
@J.F. Sebastian : Merci pour cet effort. J'éditerai probablement une liste de points de référence dans le courant de la semaine, une fois que j'aurai terminé tous mes devoirs à l'université.
0 votes
@Jf. Sebastian, le
numpy.nonzero()
: Ah ok. C'est ma faute.0 votes
@J.F. Sebastian : Je voulais juste vous faire savoir que je suis tombé sur
ValueError: dimensions too large.
quand j'ai essayé de générer des nombres premiers jusqu'à 1 milliard. Cependant, 1 million est très rapide ! 0,035 seconde sans générateur, et 0,40 seconde avec un générateur. J'ai également trouvé une optimisation supplémentaire du lien dans RCode :primes[n*n::n*n] = 0
.0 votes
Ne tenez pas compte de mon dernier commentaire sur cette optimisation. Elle est erronée.
0 votes
@Xavier Ho : Les temps que j'avais indiqués pour la solution de Casevh sont faux. Je l'ai adapté pour Python 2.x. Maintenant c'est prime_numbers : 169.00 s et prime_numbers2 : 95.53 s pour 1e9.
0 votes
@Xavier Ho : vous devriez être en mesure d'exécuter la version du générateur de numpy (qui n'utilise pas de
nonzero()
comme je l'ai montré) sur votre machine.numpy.bool
est de 1 octet, donc 4 Go devraient être suffisants sur un système neuf (non fragmenté).0 votes
@J.F. Sebastian : J'ai obtenu une erreur du genre
Value too large
d'après ce dont je me souviens. Je vous donnerai plus de détails dans un jour ou deux.0 votes
@J.F. Sebastian : Pourrais-tu poster une réponse avec la solution de numpy et la version du générateur ? Les deux gagnent largement, mais je ne peux pas créditer la personne qui l'a postée d'un tick et d'un upvote.
0 votes
Avec une limite de 1 000 000, vous devriez vraiment utiliser xrange dans votre boucle for.
5 votes
@wallacaloo : Dans Python 3.x, la gamme est paresseuse.