python-3.x optimization bit-manipulation primes sieve-of-eratosthenes

python 3.x - ¿Acelerar las operaciones bits/bits en Python?



python-3.x optimization (11)

Escribí un generador de números primos usando Sieve of Eratosthenes y Python 3.1. El código se ejecuta correctamente y con gracia en 0.32 segundos en ideone.com para generar números primos de hasta 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

El problema es que me quedo sin memoria cuando trato de generar números de hasta 1,000,000,000.

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

Como se puede imaginar, la asignación de mil millones de valores booleanos ( 1 byte 4 u 8 bytes (ver comentario) cada uno en Python) realmente no es factible, así que busqué bitstring . Pensé que usar 1 bit para cada bandera sería mucho más eficiente con la memoria. Sin embargo, el rendimiento del programa disminuyó drásticamente: 24 segundos de tiempo de ejecución, para el número primo hasta 1,000,000. Esto probablemente se deba a la implementación interna de la cadena de bits.

Puede comentar / descomentar las tres líneas para ver lo que cambié para usar BitString, como el fragmento de código anterior.

Mi pregunta es, ¿hay alguna forma de acelerar mi programa, con o sin cadena de bits?

Editar: compruebe el código usted mismo antes de publicarlo. No puedo aceptar respuestas que corran más lento que mi código existente, naturalmente.

Editar de nuevo:

He compilado una lista de puntos de referencia en mi máquina.


Aquí hay una versión que escribí hace un tiempo; podría ser interesante compararlo con el tuyo por la velocidad. Sin embargo, no hace nada sobre los problemas de espacio (de hecho, probablemente sean peores que con su versión).

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

Tengo versiones más rápidas, usando una rueda, pero son mucho más complicadas. La fuente original está here .

Bien, aquí está la versión usando una rueda. wheelSieve es el punto de entrada principal.

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]])

En cuanto a lo que es una rueda: bueno, sabes que (aparte de 2), todos los primos son impares, por lo que la mayoría de los tamices pierden todos los números pares. De manera similar, puede ir un poco más allá y observar que todos los números primos (excepto 2 y 3) son congruentes con 1 o 5 módulo 6 (== 2 * 3), así que puede salirse con la única tarea de almacenar esos números en su tamiz . El siguiente paso es observar que todos los números primos (excepto 2, 3 y 5) son congruentes con uno de 1, 7, 11, 13, 17, 19, 23, 29 (módulo 30) (aquí 30 == 2 * 3 * 5), y así sucesivamente.


De acuerdo, aquí hay una evaluación comparativa completa (casi completa) que he hecho esta noche para ver qué código se ejecuta más rápido. Con suerte, alguien encontrará esta lista útil. Omití todo lo que toma más de 30 segundos para completar en mi máquina.

Me gustaría agradecer a todos los que hicieron una aportación. Obtuve mucha información de tus esfuerzos, y espero que tú también.

Mi máquina: AMD ZM-86, Dual-Core de 2,40 Ghz, con 4 GB de RAM. Esta es una computadora portátil HP Touchsmart Tx2. Tenga en cuenta que, si bien pude haber vinculado a un pastebin, hice una evaluación comparativa de todo lo siguiente en mi propia máquina.

Agregaré el punto de referencia gmpy2 una vez que pueda construirlo.

Todos los puntos de referencia se prueban en Python 2.6 x86

Devolver una lista de números primos n hasta 1,000,000: ( usando generadores Python)

La versión de generador numpy de Sebastian (actualizada) - 121 ms @

Mark Sieve + Rueda - 154 ms

La versión de Robert con rebanado - 159 ms

Mi versión mejorada con slicing - 205 ms

Generador Numpy con enumerate - 249 ms @

Tamiz básico de Mark - 317 ms

La mejora de Casevh en mi solución original - 343 ms

Mi solución de generador numpy modificado - 407 ms

Mi método original en la pregunta - 409 ms

Solución Bitarray - 414 ms @

Pure Python con bytearray - 1394 ms @

La solución BitString de Scott - 6659 ms @

''@'' significa que este método es capaz de generar hasta n <1,000,000,000 en la configuración de mi máquina.

Además, si no necesita el generador y solo quiere toda la lista a la vez:

rosettacode.org/wiki/Sieve_of_Eratosthenes#Using_numpy - 32 ms @

(La solución numpy también es capaz de generar hasta mil millones, lo que demoró 61,6259 segundos. Sospecho que la memoria se cambió una vez, de ahí el doble de tiempo).


Hay un par de pequeñas optimizaciones para su versión. Al invertir los roles de True y False, puede cambiar " if flags[i] is False: " to " if flags[i]: ". Y el valor inicial para la declaración del segundo range puede ser i*i lugar de i*3 . Su versión original demora 0.166 segundos en mi sistema. Con esos cambios, la versión siguiente toma 0.156 segundos en mi sistema.

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

Sin embargo, esto no ayuda a tu problema de memoria.

Pasando al mundo de las extensiones C, utilicé la versión de desarrollo de gmpy . (Descargo de responsabilidad: soy uno de los mantenedores). La versión de desarrollo se llama gmpy2 y admite enteros mutables llamados xmpz. Usando gmpy2 y el siguiente código, tengo un tiempo de ejecución de 0.140 segundos. El tiempo de funcionamiento para un límite de 1,000,000,000 es 158 segundos.

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)

Al impulsar optimizaciones y sacrificar la claridad, obtengo tiempos de ejecución de 0.107 y 123 segundos con el siguiente código:

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)))

Editar: Basado en este ejercicio, modifiqué gmpy2 para aceptar xmpz.bit_set(iterator) . Usando el siguiente código, el tiempo de ejecución para todos los números primos menos de 1,000,000,000 es 56 segundos para Python 2.7 y 74 segundos para Python 3.2. (Como se señaló en los comentarios, xrange es más rápido que el 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)))

Editar # 2: ¡Un intento más! Modifiqué gmpy2 para aceptar xmpz.bit_set(slice) . Usando el siguiente código, el tiempo de ejecución para todos los números primos menos de 1,000,000,000 es de aproximadamente 40 segundos tanto para Python 2.7 como para 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)

Editar # 3: He actualizado gmpy2 para admitir correctamente el corte en el nivel de bits de un xmpz. Sin cambios en el rendimiento, pero una API muy agradable. He hecho un pequeño ajuste y he reducido el tiempo a unos 37 segundos. (Ver Edición # 4 a cambios en 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)

Editar # 4: hice algunos cambios en gmpy2 2.0.0b1 que rompen el ejemplo anterior. gmpy2 ya no trata a True como un valor especial que proporciona una fuente infinita de 1 bit. -1 debe usarse en su lugar.

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)

Edición n. ° 5: realicé algunas mejoras en gmpy2 2.0.0b2. Ahora puede iterar sobre todos los bits configurados o claros. El tiempo de ejecución ha mejorado en ~ 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))


Los tipos enteros de Python pueden ser de un tamaño arbitrario, por lo que no debería necesitar una biblioteca inteligente de bits bits para representar un conjunto de bits, solo un número.

Aquí está el código. Maneja un límite de 1,000,000 con facilidad, e incluso puede manejar 10,000,000 sin quejarse demasiado:

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 función multiples_of evita el costo de configurar cada múltiplo individual individualmente. Establece el bit inicial, lo desplaza por el paso inicial ( i << 1 ) y agrega el resultado a sí mismo. Luego dobla el paso, de modo que el siguiente desplazamiento copie ambos bits, y así sucesivamente hasta que llegue al límite. Esto establece todos los múltiplos de un número hasta el límite en el tiempo O (log (límite)).


OK, esta es mi segunda respuesta, pero como la velocidad es esencial, pensé que tenía que mencionar el módulo bitarray , a pesar de que es el bitstring de bitstring :). Es ideal para este caso, ya que no solo es una extensión C (y más rápida de lo que Python tiene la esperanza de ser), sino que también admite asignaciones de sectores. Todavía no está disponible para Python 3.

Ni siquiera he tratado de optimizar esto, simplemente reescribí la versión de cadena de bits. En mi máquina obtengo 0.16 segundos para números primos por debajo de un millón.

Por mil millones, funciona perfectamente bien y se completa en 2 minutos y 31 segundos.

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


Pregunta relacionada
La forma más rápida de listar todos los números primos debajo de N en python

Hola, también estoy buscando un código en python para generar números primos hasta 10 9 lo más rápido que pueda, lo que es difícil debido al problema de memoria. hasta ahora se me ocurrió esto para generar números primos de hasta 10 6 y 10 * 7 (0,171 y 1,764 s respectivamente en mi máquina anterior), que parece ser un poco más rápido (al menos en mi computadora) que "Mi versión mejorada" con cortar "de la publicación anterior, probablemente porque uso n // ii +1 (ver a continuación) en lugar de la len análoga (indicadores [i2 :: i << 1]) en el otro código. Por favor, corríjame si estoy equivocado. Cualquier sugerencia de mejora es muy bienvenida.

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]]

En uno de sus códigos Xavier usa banderas [i2 :: i << 1] y len (flags [i2 :: i << 1]), calculé el tamaño explícito usando el hecho de que entre i i..n tenemos ( ni i) // 2 * i múltiplos de 2 * i porque queremos contar i también sumamos 1 por lo len (tamiz [i i :: 2 * i]) igual (ni * i) // (2 * i ) +1 Esto hace que el código sea más rápido. Un generador básico basado en el código anterior sería:

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

con un poco de modificación, se puede escribir una versión un poco más lenta del código que comienza con una criba de la mitad del tamiz de tamaño = [Verdadero] * (n // 2) y funciona para el mismo n. no estoy seguro de cómo se refleja en el problema de la memoria. Como ejemplo de implementación aquí está la versión modificada del código de numeración rosetta (quizás más rápido) comenzando con un tamiz de la mitad del tamaño.

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 generador más rápido y más sabio para la memoria sería:

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

o con un poco más de código:

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 tiene alguna sugerencia sobre cómo acelerar este código, cualquier cosa, desde cambiar el orden de las operaciones hasta precomputar cualquier cosa, por favor coméntelo.


Una mejora de velocidad que puede hacer usando bitstring es usar el método ''set'' cuando excluye múltiplos del número actual.

Entonces la sección vital se vuelve

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))

En mi máquina, esto es aproximadamente 3 veces más rápido que el original.

Mi otro pensamiento fue usar la cadena de bits para representar solo los números impares. A continuación, puede encontrar los bits no configurados utilizando flags.findall([0]) que devuelve un generador. No estoy seguro si eso sería mucho más rápido (encontrar patrones de bits no es terriblemente fácil de hacer de manera eficiente).

[Divulgación completa: ¡escribí el módulo de cadena de bits, así que tengo algo de orgullo en juego aquí!]

Como una comparación, también he extraído las tripas del método de cadena de bits para que lo haga de la misma manera, pero sin verificación de rango, etc. Creo que esto da un límite inferior razonable para Python puro que funciona para mil millones de elementos (sin cambiando el algoritmo, lo que creo que es hacer trampa!)

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)

En mi máquina esto se ejecuta en aproximadamente 0,62 segundos para un millón de elementos, lo que significa que es aproximadamente una cuarta parte de la velocidad de la respuesta bitarray. Creo que es bastante razonable para Python puro.


Another really stupid option, but that can be of help if you really need a large list of primes number available very fast. Say, if you need them as a tool to answer project Euler''s problems (if the problem is just optimizing your code as a mind game it''s irrelevant).

Use any slow solution to generate list and save it to a python source file, says primes.py that would just contain:

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

Now to use them you just have to do import primes and you get them with minimal memory footprint at the speed of disk IO. Complexity is number of primes :-)

Even if you used a poorly optimized solution to generate this list, it will only be done once and it does not matter much.

You could probably make it even faster using pickle/unpickle, but you get the idea...


Here is some Python3 code that uses less memory than the bitarray/bitstring solutions posted to date and about 1/8 the memory of Robert William Hanks''s primesgen(), while running faster than primesgen() (marginally faster at 1,000,000, using 37KB of memory, 3x faster than primesgen() at 1,000,000,000 using under 34MB). Increasing the chunk size (variable chunk in the code) uses more memory but speeds up the program, up to a limit -- I picked a value so that its contribution to memory is under about 10% of sieve''s n//30 bytes. It''s not as memory-efficient as Will Ness''s infinite generator ( https://.com/a/19391111/5439078 ) because it records (and returns at the end, in compressed form) all calculated primes.

Esto debería funcionar correctamente siempre que el cálculo de la raíz cuadrada sea preciso (aproximadamente 2 ** 51 si Python usa dobles de 64 bits). Sin embargo, no deberías usar este programa para encontrar números primos tan grandes.

(No recalculé las compensaciones, simplemente las saqué del código de Robert William Hanks. ¡Gracias, 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

Nota al margen: si quiere velocidad real y tiene los 2 GB de RAM necesarios para la lista de primos a 10 ** 9, entonces debe usar pyprimesieve (en https://pypi.python.org/ , usando primesieve http://primesieve.org/ ).


One option you may want to look at is just compiling a C/C++ module so you have direct access to the bit-twiddling features. AFAIK you could write something of that nature and only return the results on completion of the calculations performed in C/C++. Now that I''m typing this you may also look at numpy which does store arrays as native C for speed. I don''t know if that will be any faster than the bitstring module, though!


You could use a segmented Sieve of Eratosthenes. The memory used for each segment is reused for the next segment.