Accélérer les opérations bitstring/bit en Python?

j'ai écrit un générateur de nombres premiers en utilisant tamis D'Eratosthène et Python 3.1. Le code fonctionne correctement et gracieusement à 0.32 secondes sur ideone.com pour générer des nombres premiers jusqu'à 1.000.000.

# from bitstring import BitString

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 = [False, False] + [True] * (limit - 2)   
#     flags = BitString(limit)
    # Step through all the odd numbers
    for i in range(3, limit, 2):       
        if flags[i] is False:
#        if flags[i] is True:
            continue
        yield i
        # Exclude further multiples of the current prime number
        if i <= sub_limit:
            for j in range(i*3, limit, i<<1):
                flags[j] = False
#                flags[j] = True

le problème est, je cours de la mémoire quand j'essaie de générer des nombres jusqu'à 1.000.000.000.

    flags = [False, False] + [True] * (limit - 2)   
MemoryError

comme vous pouvez l'imaginer, allouant 1 milliard de valeurs booléennes ( 1 byte 4 ou 8 bytes (voir commentaire) chacun en Python) est vraiment impossible, donc j'ai regardé dans bitstring . J'ai pensé qu'utiliser 1 bit pour chaque drapeau serait beaucoup plus efficace pour la mémoire. Cependant, la performance du programme a chuté drastiquement - 24 secondes d'exécution, pour le nombre premier jusqu'à 1.000.000. Ceci est probablement dû à l'implémentation interne de bitstring.

vous pouvez commenter / décommenter les trois lignes pour voir ce que j'ai changé pour utiliser BitString, comme l'extrait de code ci-dessus.

ma question Est, y a-t-il un moyen d'accélérer mon programme, avec ou sans bitstring?

Edit: s'il vous Plaît tester le code vous-même avant de poster. Je ne peux pas accepter des réponses qui fonctionnent plus lentement que mon code existant, naturellement.

Éditer à nouveau:

j'ai compilé une liste de repères sur ma machine.

38
demandé sur Community 2010-05-24 17:31:13

11 réponses

Il y a quelques petites optimisations pour votre version. En inversant les rôles de vrai et de faux, vous pouvez changer " if flags[i] is False: " en " if flags[i]: ". Et la valeur de départ pour la deuxième déclaration range peut être i*i au lieu de i*3 . Votre version originale prend 0.166 secondes sur mon système. Avec ces modifications, 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

Ce n'aide pas votre problème de mémoire.

se déplaçant dans le monde des extensions C, j'ai utilisé la version de développement de gmpy . (Disclaimer: je suis l'un des responsables. La version de développement est appelée gmpy2 et supporte des entiers mutables appelés xmpz. En utilisant gmpy2 et le code suivant, j'ai un temps de fonctionnement de 0.140 secondes. Le temps de course 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)

poussant des optimisations, et sacrifiant la clarté, je reçois 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)))

modifier: basé sur 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 moins 1.000.000.000 est de 56 secondes pour Python 2.7 et 74 secondes pour Python 3.2. (Tel que noté 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 moins 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 tranchage au niveau des bits d'une xmpz. Aucun changement dans la performance, mais une API très agréable. J'ai fait quelques ajustements et j'ai eu le temps d'environ 37 secondes. (Voir Modifier # 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 apporté quelques modifications en gmpy2 2.0.0b1 qui casse l'exemple précédent. gmpy2 ne traite plus Vrai qu'une valeur spéciale qui fournit une source infinie de 1 bits. -1 doit ê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 tous les bits qui sont définis ou clairs. Le temps de fonctionnement s'est amélioré d'environ 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))
34
répondu casevh 2012-10-12 06:05:07

ok, voici un benchmarking complet que j'ai fait ce soir pour voir quel code fonctionne le plus vite. Espérons que quelqu'un trouvera cette liste utile. J'ai omis tout ce qui prend plus de 30 secondes à remplir sur ma machine.

je tiens à remercier tous ceux qui ont contribué. J'ai appris beaucoup de choses grâce à vos efforts, et j'espère que vous aussi.

ma machine: AMD ZM-86, 2.40 Ghz Dual-Core, avec 4 Go de RAM. C'est un portable HP Touchsmart Tx2. Notez que même si j'ai peut-être fait un lien avec un pastebin, , j'ai comparé tout ce qui suit sur ma propre machine.

j'ajouterai le benchmark gmpy2 une fois que je serai capable de le construire.

tous les benchmarks sont testés en python 2.6 x86

retour d'une liste de nombres premiers n jusqu'à 1.000.000: ( utilisant Python générateurs)

de Sebastian numpy générateur de version (mise à jour) - 121 ms @

tamis de Mark + roue - 154 ms

de Robert version avec découpage 1519210920" - 159 ms

Ma version améliorée avec de découpage - 205 ms

Numpy générateur avec énumérer - 249 ms @

tamis de base de Mark - 317 ms

amélioration de casevh sur mon original solution - 343 ms

Mon modifiée numpy générateur de solution - 407 ms

Ma méthode originale dans le question - 409 ms

Bitarray Solution - 414 ms @

Python pur avec bytearray - 1394 ms @

Scott's BitString solution - 6659 ms @

'@' signifie que cette méthode est capable de générer jusqu'à n < 1 000 000 000 d', sur mon installation de machine.

en outre, si vous n'avez pas besoin de génératrice et je veux juste toute la liste tout de suite:

numpy solution from RosettaCode - 32 ms @

(la solution de numpy est également capable de générer jusqu'à 1 milliard, ce qui a pris 61.6259 secondes. Je pense que la mémoire a été échangée une fois, d'où le double temps.)

8
répondu Xavier Ho 2017-05-23 12:02:48

OK, donc c'est ma deuxième réponse, mais comme la vitesse est essentielle, j'ai pensé que je devais mentionner le module bitarray - même si c'est bitstring nemesis :). Il est idéalement adapté à ce cas car non seulement c'est une extension C (et donc plus rapide que Python pur a un espoir d'être), mais il supporte aussi les assignations de tranches. Il n'est pas encore disponible pour Python 3.

Je n'ai même pas essayé d'optimiser cela, je j'ai réécrit la version bitstring. Sur ma machine, j'ai 0,16 seconde pour des nombres premiers inférieurs à un million.

pour un milliard, il fonctionne parfaitement et se termine en 2 minutes et 31 secondes.

import bitarray

def prime_bitarray(limit=1000000):
    yield 2
    flags = bitarray.bitarray(limit)
    flags.setall(False)
    sub_limit = int(limit**0.5)
    for i in xrange(3, limit, 2):
        if not flags[i]:
            yield i
            if i <= sub_limit:
                flags[3*i:limit:i*2] = True
8
répondu Scott Griffiths 2018-02-07 15:12:11

question connexe.

la manière la plus rapide de lister tous les nombres premiers ci-dessous N en python

Salut, je suis trop à la recherche d'un code en python pour générer des nombres premiers jusqu'à 10 9 aussi vite que je peux, ce qui est difficile à cause du problème de mémoire. jusqu'à présent, j'ai créé ceci pour générer des nombres premiers jusqu'à 10 6 & 10*7 (0,171 s et 1,764 s respectivement dans mon ancienne machine), qui semble être un peu plus rapide(au moins dans ma ordinateur) que "ma version améliorée avec tranchage" du post précédent, probablement parce que j'utilise n//i-i +1 (Voir ci-dessous)à la place de Len analogue(drapeaux[i2::i<<1]) dans l'autre code. s'il vous plaît corrigez-moi si je me trompe. Toute suggestion d'amélioration sont les bienvenus.

def primes(n):
    """ Returns  a list of primes < n """
    sieve = [True] * n
    for i in xrange(3,int(n**0.5)+1,2):
        if sieve[i]:
            sieve[i*i::2*i]=[False]*((n-i*i-1)/(2*i)+1)
    return [2] + [i for i in xrange(3,n,2) if sieve[i]]

dans L'un de ses codes Xavier utilise les drapeaux[i2::i<<1] et len(drapeaux[i2::i<<1]), j'ai calculé la taille explicite en utilisant le fait qu'entre i I..n Nous avons (N-I I)//2*I multiples de 2*I parce que nous voulons Compter i i aussi nous sommons 1 ainsi Len(tamis[i i::2*i]) égal (n-i*i)//(2*i) +1 cela rend le code plus rapide. Un générateur de base basé sur le code ci-dessus serait:

def primesgen(n):
    """ Generates all primes <= n """
    sieve = [True] * n
    yield 2
    for i in xrange(3,int(n**0.5)+1,2):
        if sieve[i]:
            yield i
            sieve[i*i::2*i] = [False]*((n-i*i-1)/(2*i)+1)
    for i in xrange(i+2,n,2):
        if sieve[i]: yield i

avec un peu de modification on peut écrire une version légèrement plus lente du code ci-dessus qui commence avec un tamis moitié de la taille tamis = [True] * (n//2) et fonctionne pour le même N. Je ne sais pas comment vous réfléchissez dans la question de la mémoire. Comme un exemple de mise en œuvre ici est la version modifiée du code num PY rosetta (peut-être plus rapide) à partir d'un tamis de la moitié de la taille.

import numpy
def primesfrom3to(n):
    """ Returns a array of primes, 3 <= p < n """
    sieve = numpy.ones(n/2, dtype=numpy.bool)
    for i in xrange(3,int(n**0.5)+1,2):
        if sieve[i/2]: sieve[i*i/2::i] = False
    return 2*numpy.nonzero(sieve)[0][1::]+1

un générateur plus rapide et plus économe en mémoire serait:

import numpy
def primesgen1(n):
""" Input n>=6, Generates all primes < n """
sieve1 = numpy.ones(n/6+1, dtype=numpy.bool)
sieve5 = numpy.ones(n/6  , dtype=numpy.bool)
sieve1[0] = False
yield 2; yield 3;
for i in xrange(int(n**0.5)/6+1):
    if sieve1[i]:
        k=6*i+1; yield k;
        sieve1[ ((k*k)/6) ::k] = False
        sieve5[(k*k+4*k)/6::k] = False
    if sieve5[i]:
        k=6*i+5; yield k;
        sieve1[ ((k*k)/6) ::k] = False
        sieve5[(k*k+2*k)/6::k] = False
for i in xrange(i+1,n/6):
        if sieve1[i]: yield 6*i+1
        if sieve5[i]: yield 6*i+5
if n%6 > 1:
    if sieve1[i+1]: yield 6*(i+1)+1

ou avec un peu plus de code:

import numpy
def primesgen(n):
    """ Input n>=30, Generates all primes < n """
    size = n/30 + 1
    sieve01 = numpy.ones(size, dtype=numpy.bool)
    sieve07 = numpy.ones(size, dtype=numpy.bool)
    sieve11 = numpy.ones(size, dtype=numpy.bool)
    sieve13 = numpy.ones(size, dtype=numpy.bool)
    sieve17 = numpy.ones(size, dtype=numpy.bool)
    sieve19 = numpy.ones(size, dtype=numpy.bool)
    sieve23 = numpy.ones(size, dtype=numpy.bool)
    sieve29 = numpy.ones(size, dtype=numpy.bool)
    sieve01[0] = False
    yield 2; yield 3; yield 5;
    for i in xrange(int(n**0.5)/30+1):
        if sieve01[i]:
            k=30*i+1; yield k;
            sieve01[     (k*k)/30::k] = False
            sieve07[(k*k+ 6*k)/30::k] = False
            sieve11[(k*k+10*k)/30::k] = False
            sieve13[(k*k+12*k)/30::k] = False
            sieve17[(k*k+16*k)/30::k] = False
            sieve19[(k*k+18*k)/30::k] = False
            sieve23[(k*k+22*k)/30::k] = False
            sieve29[(k*k+28*k)/30::k] = False
        if sieve07[i]:
            k=30*i+7; yield k;
            sieve01[(k*k+ 6*k)/30::k] = False
            sieve07[(k*k+24*k)/30::k] = False
            sieve11[(k*k+16*k)/30::k] = False
            sieve13[(k*k+12*k)/30::k] = False
            sieve17[(k*k+ 4*k)/30::k] = False
            sieve19[     (k*k)/30::k] = False
            sieve23[(k*k+22*k)/30::k] = False
            sieve29[(k*k+10*k)/30::k] = False
        if sieve11[i]:
            k=30*i+11; yield k;
            sieve01[     (k*k)/30::k] = False
            sieve07[(k*k+ 6*k)/30::k] = False
            sieve11[(k*k+20*k)/30::k] = False
            sieve13[(k*k+12*k)/30::k] = False
            sieve17[(k*k+26*k)/30::k] = False
            sieve19[(k*k+18*k)/30::k] = False
            sieve23[(k*k+ 2*k)/30::k] = False
            sieve29[(k*k+ 8*k)/30::k] = False
        if sieve13[i]:
            k=30*i+13; yield k;
            sieve01[(k*k+24*k)/30::k] = False
            sieve07[(k*k+ 6*k)/30::k] = False
            sieve11[(k*k+ 4*k)/30::k] = False
            sieve13[(k*k+18*k)/30::k] = False
            sieve17[(k*k+16*k)/30::k] = False
            sieve19[     (k*k)/30::k] = False
            sieve23[(k*k+28*k)/30::k] = False
            sieve29[(k*k+10*k)/30::k] = False
        if sieve17[i]:
            k=30*i+17; yield k;
            sieve01[(k*k+ 6*k)/30::k] = False
            sieve07[(k*k+24*k)/30::k] = False
            sieve11[(k*k+26*k)/30::k] = False
            sieve13[(k*k+12*k)/30::k] = False
            sieve17[(k*k+14*k)/30::k] = False
            sieve19[     (k*k)/30::k] = False
            sieve23[(k*k+ 2*k)/30::k] = False
            sieve29[(k*k+20*k)/30::k] = False
        if sieve19[i]:
            k=30*i+19; yield k;
            sieve01[     (k*k)/30::k] = False
            sieve07[(k*k+24*k)/30::k] = False
            sieve11[(k*k+10*k)/30::k] = False
            sieve13[(k*k+18*k)/30::k] = False
            sieve17[(k*k+ 4*k)/30::k] = False
            sieve19[(k*k+12*k)/30::k] = False
            sieve23[(k*k+28*k)/30::k] = False
            sieve29[(k*k+22*k)/30::k] = False
        if sieve23[i]:
            k=30*i+23; yield k;
            sieve01[(k*k+24*k)/30::k] = False
            sieve07[(k*k+ 6*k)/30::k] = False
            sieve11[(k*k+14*k)/30::k] = False
            sieve13[(k*k+18*k)/30::k] = False
            sieve17[(k*k+26*k)/30::k] = False
            sieve19[     (k*k)/30::k] = False
            sieve23[(k*k+ 8*k)/30::k] = False
            sieve29[(k*k+20*k)/30::k] = False
        if sieve29[i]:
            k=30*i+29; yield k;
            sieve01[     (k*k)/30::k] = False
            sieve07[(k*k+24*k)/30::k] = False
            sieve11[(k*k+20*k)/30::k] = False
            sieve13[(k*k+18*k)/30::k] = False
            sieve17[(k*k+14*k)/30::k] = False
            sieve19[(k*k+12*k)/30::k] = False
            sieve23[(k*k+ 8*k)/30::k] = False
            sieve29[(k*k+ 2*k)/30::k] = False
    for i in xrange(i+1,n/30):
            if sieve01[i]: yield 30*i+1
            if sieve07[i]: yield 30*i+7
            if sieve11[i]: yield 30*i+11
            if sieve13[i]: yield 30*i+13
            if sieve17[i]: yield 30*i+17
            if sieve19[i]: yield 30*i+19
            if sieve23[i]: yield 30*i+23
            if sieve29[i]: yield 30*i+29
    i += 1
    mod30 = n%30
    if mod30 > 1:
        if sieve01[i]: yield 30*i+1
    if mod30 > 7:
        if sieve07[i]: yield 30*i+7
    if mod30 > 11:
        if sieve11[i]: yield 30*i+11
    if mod30 > 13:
        if sieve13[i]: yield 30*i+13
    if mod30 > 17:
        if sieve17[i]: yield 30*i+17
    if mod30 > 19:
        if sieve19[i]: yield 30*i+19
    if mod30 > 23:
        if sieve23[i]: yield 30*i+23

Ps: Si vous avez des suggestions sur la façon d'accélérer ce code, n'importe quoi de l'ordre de changement des opérations au pré-calcul n'importe quoi, s'il vous plaît commenter.

5
répondu Robert William Hanks 2017-05-23 10:30:56

Voici une version que j'ai écrite il y a un certain temps; il pourrait être intéressant de comparer avec la vôtre pour la vitesse. Il ne fait rien au sujet des problèmes d'espace, cependant (en fait, ils sont probablement pires qu'avec votre version).

from math import sqrt

def basicSieve(n):
    """Given a positive integer n, generate the primes < n."""
    s = [1]*n
    for p in xrange(2, 1+int(sqrt(n-1))):
        if s[p]:
            a = p*p
            s[a::p] = [0] * -((a-n)//p)
    for p in xrange(2, n):
        if s[p]:
            yield p 

j'ai des versions plus rapides, utilisant une roue, mais elles sont beaucoup plus compliquées. La source originale est ici .

ok, voici la version avec une roue. wheelSieve est la principale entrée point.

from math import sqrt
from bisect import bisect_left

def basicSieve(n):
    """Given a positive integer n, generate the primes < n."""
    s = [1]*n
    for p in xrange(2, 1+int(sqrt(n-1))):
        if s[p]:
            a = p*p
            s[a::p] = [0] * -((a-n)//p)
    for p in xrange(2, n):
        if s[p]:
            yield p

class Wheel(object):
    """Class representing a wheel.

    Attributes:
       primelimit -> wheel covers primes < primelimit.
       For example, given a primelimit of 6
       the wheel primes are 2, 3, and 5.
       primes -> list of primes less than primelimit
       size -> product of the primes in primes;  the modulus of the wheel
       units -> list of units modulo size
       phi -> number of units

    """
    def __init__(self, primelimit):
        self.primelimit = primelimit
        self.primes = list(basicSieve(primelimit))

        # compute the size of the wheel
        size = 1
        for p in self.primes:
            size *= p
        self.size = size

        # find the units by sieving
        units = [1] * self.size
        for p in self.primes:
            units[::p] = [0]*(self.size//p)
        self.units = [i for i in xrange(self.size) if units[i]]

        # number of units
        self.phi = len(self.units)

    def to_index(self, n):
        """Compute alpha(n), where alpha is an order preserving map
        from the set of units modulo self.size to the nonnegative integers.

        If n is not a unit, the index of the first unit greater than n
        is given."""
        return bisect_left(self.units, n % self.size) + n // self.size * self.phi

    def from_index(self, i):
        """Inverse of to_index."""

        return self.units[i % self.phi] + i // self.phi * self.size

def wheelSieveInner(n, wheel):
    """Given a positive integer n and a wheel, find the wheel indices of
    all primes that are less than n, and that are units modulo the
    wheel modulus.
    """

    # renaming to avoid the overhead of attribute lookups
    U = wheel.units
    wS = wheel.size
    # inverse of unit map
    UI = dict((u, i) for i, u in enumerate(U))
    nU = len(wheel.units)

    sqroot = 1+int(sqrt(n-1)) # ceiling of square root of n

    # corresponding index (index of next unit up)
    sqrti = bisect_left(U, sqroot % wS) + sqroot//wS*nU
    upper = bisect_left(U, n % wS) + n//wS*nU
    ind2 = bisect_left(U, 2 % wS) + 2//wS*nU

    s = [1]*upper
    for i in xrange(ind2, sqrti):
        if s[i]:
            q = i//nU
            u = U[i%nU]
            p = q*wS+u
            u2 = u*u
            aq, au = (p+u)*q+u2//wS, u2%wS
            wp = p * nU
            for v in U:
                # eliminate entries corresponding to integers
                # congruent to p*v modulo p*wS
                uvr = u*v%wS
                m = aq + (au > uvr)
                bot = (m + (q*v + u*v//wS - m) % p) * nU + UI[uvr]
                s[bot::wp] = [0]*-((bot-upper)//wp)
    return s

def wheelSieve(n, wheel=Wheel(10)):
    """Given a positive integer n, generate the list of primes <= n."""
    n += 1
    wS = wheel.size
    U = wheel.units
    nU = len(wheel.units)
    s = wheelSieveInner(n, wheel)
    return (wheel.primes[:bisect_left(wheel.primes, n)] +
            [p//nU*wS + U[p%nU] for p in xrange(bisect_left(U, 2 % wS)
             + 2//wS*nU, len(s)) if s[p]])

ce Que la roue est: bien, vous savez que (hormis 2), tous les nombres premiers sont impairs, donc la plupart des tamis manquer tous les nombres pairs. De même, vous pouvez aller un peu plus loin et noter que tous les nombres premiers (sauf 2 et 3) sont congruents à 1 ou 5 modulo 6 (== 2 * 3), de sorte que vous pouvez vous en tirer avec seulement le stockage des entrées pour ces nombres dans votre tamis. L'étape suivante consiste à noter que tous les nombres premiers (sauf 2, 3 et 5) sont congruents à l'un des nombres suivants: 1, 7, 11, 13, 17, 19, 23, 29 (modulo 30) (ici 30 == 2*3*5), et ainsi de suite.

4
répondu Mark Dickinson 2010-05-24 15:25:18

une amélioration de la vitesse que vous pouvez faire en utilisant bitstring est d'utiliser la méthode 'set' lorsque vous excluez des multiples du nombre courant.

ainsi la section vitale devient

for i in range(3, limit, 2):
    if flags[i]:
        yield i
        if i <= sub_limit:
            flags.set(1, range(i*3, limit, i*2))    

sur ma machine cela fonctionne environ 3 fois plus vite que l'original.

mon autre pensée était d'utiliser la chaîne de bits pour représenter seulement les nombres impairs. Vous pouvez alors trouver les bits non connectés en utilisant flags.findall([0]) qui renvoie un générateur. Pas sûr si ce serait beaucoup plus rapide (Trouver des modèles de bits n'est pas terriblement facile à faire efficacement).

[divulgation complète: j'ai écrit le module bitstring, donc j'ai une certaine fierté en jeu ici!]


en comparaison, j'ai aussi retiré les intestins de la méthode bitstring de sorte qu'il le fait de la même manière, mais sans vérification de la portée, etc. Je pense que cela donne une limite inférieure raisonnable pour Python pur qui fonctionne pour un milliard d'éléments (sans changer l'algorithme, ce qui, je pense, c'est de la triche!)

def prime_pure(limit=1000000):
    yield 2
    flags = bytearray((limit + 7) // 8)
    sub_limit = int(limit**0.5)
    for i in xrange(3, limit, 2):
        byte, bit = divmod(i, 8)
        if not flags[byte] & (128 >> bit):
            yield i
            if i <= sub_limit:
                for j in xrange(i*3, limit, i*2):
                    byte, bit = divmod(j, 8)
                    flags[byte] |= (128 >> bit)

sur ma machine, cela prend environ 0,62 seconde pour un million d'éléments, ce qui signifie que c'est environ un quart de la vitesse de la réponse bitarray. Je pense que c'est tout à fait raisonnable pour Python pur.

3
répondu Scott Griffiths 2010-07-25 21:56:29

les types entiers de Python peuvent être de taille arbitraire, donc vous ne devriez pas avoir besoin d'une bibliothèque de bitstring intelligente pour représenter un ensemble de bits, juste un seul nombre.

voici le code. Il gère une limite de 1.000.000 avec facilité, et peut même gérer 10.000.000 sans se plaindre trop:

def multiples_of(n, step, limit):
    bits = 1 << n
    old_bits = bits
    max = 1 << limit
    while old_bits < max:
        old_bits = bits
        bits += bits << step
        step *= 2
    return old_bits

def prime_numbers(limit=10000000):
    '''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 = ((1 << (limit - 2)) - 1) << 2
    # Step through all the odd numbers                                                                                  
    for i in xrange(3, limit, 2):
        if not (flags & (1 << i)):
            continue
        yield i
        # Exclude further multiples of the current prime number                                                         
        if i <= sub_limit:
            flags &= ~multiples_of(i * 3, i << 1, limit)

la fonction multiples_of évite le coût de réglage de chaque multiple individuellement. Il règle le bit initial, le déplace par l'étape initiale ( i << 1 ) et ajoute le résultat à lui-même. Il Double alors le pas, de sorte que le prochain décalage copie les deux bits, et ainsi de suite jusqu'à ce qu'il atteigne la limite. Cela règle Tous les multiples d'un nombre jusqu'à la limite dans le temps o(log(limite)).

2
répondu Marcelo Cantos 2010-05-24 14:21:56

l'une des options que vous voudrez peut-être Regarder est de compiler un module C/C++ pour avoir un accès direct aux fonctionnalités de "bit-twiddling". AFAIK vous pouvez écrire quelque chose de cette nature et ne retourner les résultats qu'à la fin des calculs effectués en C/C++. Maintenant que je tape ceci, vous pouvez aussi regarder numpy qui stocke des tableaux en tant que c natif pour la vitesse. Je ne sais pas si ce sera plus rapide que le module bitstring!

2
répondu Wayne Werner 2010-05-24 14:29:45

une autre option vraiment stupide, mais cela peut être d'une aide si vous avez vraiment besoin d'une grande liste de nombres premiers disponibles très rapidement. Par exemple, si vous avez besoin d'eux comme un outil pour répondre aux problèmes du projet Euler (si le problème est juste l'optimisation de votre code comme un jeu d'esprit, il est hors de propos).

utilisez n'importe quelle solution lente pour générer la liste et la sauvegarder dans un fichier source python, dit primes.py qui contiendrait juste:

primes = [ a list of a million primes numbers here ]

maintenant pour les utiliser vous venez vous devez faire import primes et vous les obtenez avec une empreinte mémoire minimale à la vitesse du disque IO. La complexité est le nombre de nombres premiers: -)

même si vous avez utilisé une solution mal optimisée pour générer cette liste, cela ne sera fait qu'une seule fois et cela n'a pas beaucoup d'importance.

vous pourriez probablement le rendre encore plus rapide en utilisant pickle / unpickle, mais vous avez l'idée...

1
répondu kriss 2010-05-27 00:36:23

vous pourriez utiliser un tamis segmenté de Eratosthènes. La mémoire utilisée pour chaque segment est réutilisée pour le segment suivant.

0
répondu user448810 2012-02-02 13:58:07

voici un code Python3 qui utilise moins de mémoire que les solutions bitarray/bitstring postées à ce jour et environ 1/8 de la mémoire de primesgen () de Robert William Hanks, tout en courant plus vite que primesgen() (légèrement plus rapide à 1.000.000, en utilisant 37KB de mémoire, 3x plus rapide que primesgen() à 1.000.000.000 en utilisant moins de 34MB). Augmenter la taille du morceau (variable chunk dans le code) utilise plus de mémoire mais accélère le programme, jusqu'à une limite -- j'ai choisi une valeur de sorte que sa contribution à la mémoire est sous environ 10% de n//30 octets de tamis. Il n'est pas aussi efficace en mémoire que le générateur infini de Will Ness ( https://stackoverflow.com/a/19391111/5439078 ) parce qu'il enregistre (et retourne à la fin, sous forme comprimée) tous les nombres premiers calculés.

cela devrait fonctionner correctement tant que le calcul à la racine carrée est exact (environ 2**51 si Python utilise des doubles 64 bits). Cependant, vous ne devriez pas utiliser ce programme pour trouver des nombres premiers aussi grands!

(Je n'ai pas recalculé les offsets, je les ai juste pris du code de Robert William Hanks. Merci Robert!)

import numpy as np
def prime_30_gen(n):
    """ Input n, int-like.  Yields all primes < n as Python ints"""
    wheel = np.array([2,3,5])
    yield from wheel[wheel < n].tolist()
    powers = 1 << np.arange(8, dtype='u1')
    odds = np.array([1, 7, 11, 13, 17, 19, 23, 29], dtype='i8')
    offsets=np.array([[0,6,10,12,16,18,22,28],[6,24,16,12,4,0,22,10],
                      [0,6,20,12,26,18,2,8],  [24,6,4,18,16,0,28,10],
                      [6,24,26,12,14,0,2,20], [0,24,10,18,4,12,28,22],
                      [24,6,14,18,26,0,8,20], [0,24,20,18,14,12,8,2]],
                     dtype='i8')
    offsets = {d:f for d,f in zip(odds, offsets)}
    sieve = 255 * np.ones((n + 28) // 30, dtype='u1')
    if n % 30 > 1: sieve[-1] >>= 8 - odds.searchsorted(n % 30)
    sieve[0] &= ~1 
    for i in range((int((n - 1)**0.5) + 29) // 30):
        for odd in odds[(sieve[i] & powers).nonzero()[0]]:
            k = i * 30 + odd
            yield int(k)
            for clear_bit, off in zip(~powers, offsets[odd]): 
                sieve[(k * (k + off)) // 30 :: k] &= clear_bit
    chunk = min(1 + (n >> 13), 1<<15)
    for i in range(i + 1, len(sieve), chunk):
        a = (sieve[i : i + chunk, np.newaxis] & powers)
        a = np.flatnonzero(a.astype('bool')) + i * 8
        a = (a // 8 * 30 + odds[a & 7]).tolist()
        yield from a
    return sieve

Side note: si vous voulez une vitesse réelle et que vous avez les 2 Go de RAM nécessaires pour la liste des nombres premiers à 10* * 9, alors vous devez utiliser typrimesieve (sur https://pypi.python.org / , en utilisant primesieve http://primesieve.org / ).

0
répondu Jason 2017-05-23 12:26:38