我使用Sieve of Eratosthenes和Python 3.1编写了素数生成器。代码在ideone.com上以0.32秒正确且正常地运行,以生成高达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
问题是,当我尝试生成高达1,000,000,000的数字时,内存不足。
flags = [False, False] + [True] * (limit - 2)
MemoryError
可以想象,在Python中分配10亿个布尔值( 1个字节 4或8个字节(请参阅注释))实际上是不可行的,所以我查看了bitstring。我想,每个标志使用1位将更加节省内存。然而,该程序的性能急剧下降 - 运行时间为24秒,素数高达1,000,000。这可能是由于bitstring的内部实现。
您可以评论/取消注释这三行,以查看我更改为使用BitString的内容,作为上面的代码段。
我的问题是,有没有办法加速我的程序,有或没有位串?
编辑:请在发布前自行测试代码。我自然不能接受比现有代码慢的答案。
再次修改:
答案 0 :(得分:34)
您的版本有一些小优化。通过反转True和False的角色,您可以将“if flags[i] is False:
”更改为“if flags[i]:
”。第二个range
语句的起始值可以是i*i
,而不是i*3
。您的原始版本在我的系统上需要0.166秒。通过这些更改,我的系统下面的版本需要0.156秒。
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
但这对你的记忆问题没有帮助。
进入C扩展世界,我使用了gmpy的开发版本。 (免责声明:我是维护者之一。)开发版本称为gmpy2,支持名为xmpz的可变整数。使用gmpy2和以下代码,我的运行时间为0.140秒。限制为1,000,000,000的运行时间为158秒。
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)
推动优化并牺牲清晰度,使用以下代码获得0.107和123秒的运行时间:
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)))
编辑:根据此练习,我修改了gmpy2以接受xmpz.bit_set(iterator)
。使用以下代码,所有素数少于1,000,000,000的运行时间对于Python 2.7为56秒,对于Python 3.2为74秒。 (如评论中所述,xrange
比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)))
编辑#2:再试一次!我修改了gmpy2以接受xmpz.bit_set(slice)
。使用以下代码,Python 2.7和Python 3.2的所有质数小于1,000,000,000的运行时间约为40秒。
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)
编辑#3:我已经更新了gmpy2以正确支持xmpz位级别的切片。性能没有变化,但是API非常好。我做了一点调整,我的时间缩短了大约37秒。 (请参阅编辑#4以了解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)
编辑#4:我在gmpy2 2.0.0b1中做了一些改动,打破了前面的例子。 gmpy2不再将True视为提供1位无限来源的特殊值。应该使用-1代替。
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)
编辑#5:我对gmpy2 2.0.0b2做了一些改进。您现在可以迭代所有已设置或清除的位。运行时间提高了约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))
答案 1 :(得分:8)
好的,所以这是我的第二个答案,但由于速度至关重要,我认为我必须提到bitarray模块 - 即使它是bitstring的克星:)。它非常适合这种情况,因为它不仅是一个C扩展(并且比纯Python更有希望),但它也支持切片分配。但它尚不适用于Python 3。
我甚至没有尝试优化它,我只是重写了bitstring版本。在我的机器上,我得到0.16秒的素数低于一百万。
十亿,它运行得非常好,并在2分31秒内完成。
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
答案 2 :(得分:8)
好的,这是一个(近乎完整的)全面的基准测试我今晚已经完成,看看哪个代码运行速度最快。希望有人会发现这个列表很有用。我省略了在我的机器上完成任何需要超过30秒的事情。
我要感谢所有投入的人。我从你的努力中获得了很多洞察力,我希望你也有。
我的机器:AMD ZM-86,2.40 Ghz双核,4GB内存。这是一台HP Touchsmart Tx2笔记本电脑。请注意,虽然我可能已链接到pastebin,但我在自己的计算机上对以下所有内容进行了基准测试。
一旦我能够构建它,我将添加gmpy2基准。
所有基准测试都在Python 2.6 x86中进行测试
返回最多1,000,000的素数列表:(使用 Python 发电机)强>
Sebastian's numpy generator version (updated) - 121 ms @
Mark's Sieve + Wheel - 154 ms
Robert's version with slicing - 159 ms
My improved version with slicing - 205毫秒
Numpy generator with enumerate - 249 ms @
Mark's Basic Sieve - 317 ms
casevh's improvement on my original solution - 343 ms
My modified numpy generator solution - 407 ms
My original method in the question - 409 ms
Bitarray Solution - 414 ms @
Pure Python with bytearray - 1394 ms @
Scott's BitString solution - 6659 ms @
'@'表示该方法能够产生高达n
另外,如果你不需要的话 生成器,只想要整个列表 马上:
numpy solution from RosettaCode - 32毫秒@
(numpy解决方案也能够产生高达10亿,这需要61.6259秒。我怀疑内存被交换一次,因此是双倍时间。)
答案 3 :(得分:6)
相关问题:Fastest way to list all primes below N in python。
嗨,我也在寻找Python中的代码来尽可能快地生成 10 ^ 9 的素数,这很难因为内存问题。到目前为止,我想出了这个来生成 10 ^ 6 &amp;的质数。 10 ^ 7 (在我的旧机器上分别计时0,171s和1,764s),这似乎比“我的改进版本切片”更快(至少在我的电脑中)< / em>来自之前的帖子,可能是因为我使用n//i-i +1
(见下文)而不是其他代码中的类似len(flags[i2::i<<1])
。如果我错了,请纠正我。任何改进建议都是非常受欢迎的。
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]]
在他的一个代码中,Xavier使用flags[i2::i<<1]
和len(flags[i2::i<<1])
。我明确地计算了大小,使用i*i..n
之间我们(n-i*i)//2*i
的{{1}}倍的事实,因为我们想要计算2*i
,我们也总计i*i
所以{ {1}}等于1
。这使代码更快。基于上述代码的基本生成器将是:
len(sieve[i*i::2*i])
通过一些修改,可以编写一个稍慢版本的代码,以一半大小为(n-i*i)//(2*i) +1
的筛子开始,并且适用于相同的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
。不确定这将如何反映在内存问题上。作为一个实现的例子,这里是
numpy rosetta代码的修改版本(可能更快)从一半大小的筛子开始。
sieve = [True] * (n//2)
更快更好更多内存生成器将是:
n
或者使用更多代码:
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
Ps:如果您对如何加快此代码提出任何建议,从更改操作顺序到预先计算任何内容,请发表评论。
答案 4 :(得分:4)
这是我之前写的一个版本;与你的速度进行比较可能会很有趣。它对空间问题没有任何作用(事实上,它们可能比你的版本更糟)。
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
我有更快的版本,使用滚轮,但它们要复杂得多。原始来源为here。
好的,这是使用滚轮的版本。 wheelSieve
是主要的切入点。
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]])
关于什么是轮子:嗯,你知道(除了2),所有素数都是奇数,所以大多数筛子错过了所有偶数。类似地,您可以进一步注意所有质数(除了2和3)与1或5模6(== 2 * 3)一致,因此您只能在筛子中存储这些数字的条目。下一步是要注意所有素数(2,3和5除外)与1,7,11,13,17,19,23,29(模30)中的一个一致(这里30 == 2 * 3 * 5),等等。
答案 5 :(得分:3)
使用bitstring可以提高速度是在排除当前数字的倍数时使用'set'方法。
所以重要的部分变成
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))
在我的机器上,它比原来的速度快3倍。
我的另一个想法是使用bitstring来表示奇数。然后,您可以使用返回生成器的flags.findall([0])
找到未设置的位。不确定这是否会更快(找到位模式并不是非常容易有效)。
[完全披露:我写了bitstring模块,所以我在这里有一些自豪感!]
作为比较,我还从bittring方法中获取了内容,以便它以相同的方式执行,但是没有范围检查等。我认为这为纯粹的Python提供了合理的下限元素(不改变算法,我认为这是作弊!)
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)
在我的机器上,对于一百万个元素,它在大约0.62秒内运行,这意味着它大约是比特阵列答案速度的四分之一。我认为纯Python非常合理。
答案 6 :(得分:2)
Python的整数类型可以是任意大小,因此您不需要一个聪明的位串库来表示一组位,只需一个数字。
这是代码。它可以轻松处理1,000,000的限制,甚至可以处理10,000,000而不会抱怨太多:
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)
multiples_of
功能可以避免单独设置每个单独的费用。它设置初始位,将其移位初始步(i << 1
)并将结果添加到自身。然后它将步骤加倍,以便下一个移位复制两个位,依此类推,直到达到极限。这会将数字的所有倍数设置为O(log(限制))时间内的限制。
答案 7 :(得分:2)
您可能想要查看的一个选项是编译C / C ++模块,这样您就可以直接访问比特错误的功能。 AFAIK你可以编写一些这种性质的东西,只有在完成C / C ++中执行的计算时才会返回结果。现在我正在键入这个你也可以看看numpy,它将数组存储为本机C的速度。我不知道是否会比bitstring模块更快!
答案 8 :(得分:1)
另一个非常愚蠢的选择,但如果你真的需要一个非常快的可用数量的大量素数,这可能会有所帮助。比如说,如果你需要它们作为回答项目Euler问题的工具(如果问题只是优化你的代码作为心智游戏那么无关紧要)。
使用任何慢速解决方案生成列表并将其保存到python源文件,primes.py
只包含:
primes = [ a list of a million primes numbers here ]
现在要使用它们,您只需要执行import primes
,并以磁盘IO的速度获得最小的内存占用。复杂性是素数: - )
即使您使用了经过优化的解决方案来生成此列表,它也只会执行一次并且无关紧要。
使用pickle / unpickle你可能会更快,但你明白了......
答案 9 :(得分:1)
你可以使用分段的Eratosthenes筛子。用于每个段的内存将重新用于下一个段。
答案 10 :(得分:1)
以下是一些Python3代码,它使用的内存少于迄今发布的bitarray / bitstring解决方案,大约是Robert William Hanks的primesgen()内存的1/8,而运行速度比primesgen()快得多(略高于1,000,000,使用37KB的内存,比使用34MB的1,000,000,000的primesgen()快3倍。增加块大小(代码中的变量块)使用更多内存但加速程序,达到极限 - 我选择了一个值,使其对内存的贡献低于筛子的n // 30字节的10%。它不像Will Ness's infinite generator那样具有内存效率(另请参阅https://stackoverflow.com/a/19391111/5439078),因为它记录(并以压缩形式返回结尾)所有计算的素数。
只要平方根计算准确(如果Python使用64位双精度,大约2 ** 51),这应该可以正常工作。但是你不应该使用这个程序来找到那么大的素数!
(我没有重新计算偏移量,只是从Robert William Hanks的代码中取出它们。感谢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
旁注:如果你想要真正的速度并且要求素数列表需要2GB的RAM到10 ** 9,那么你应该使用pyprimesieve(在https://pypi.python.org/上,使用primesieve http://primesieve.org/)