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
ご想像のとおり、10億のブール値を割り当てます(1バイト Pythonではそれぞれ4バイトまたは8バイト(コメントを参照)は実際には実行不可能なので、 bitstring を調べました。各フラグに1ビットを使用すると、メモリ効率がはるかに高くなると思いました。ただし、プログラムのパフォーマンスは大幅に低下しました。素数が最大1,000,000の場合、実行時間は24秒です。これはおそらくビットストリングの内部実装によるものです。
上記のコードスニペットのように、3行をコメント化/コメント解除して、BitStringを使用するために何を変更したかを確認できます。
私の質問は、ビット文字列の有無にかかわらず、プログラムを高速化する方法はありますか?
編集:投稿する前に自分でコードをテストしてください。当然、既存のコードよりも実行が遅い回答を受け入れることはできません。
再編集:
お使いのバージョンには、いくつかの小さな最適化があります。 TrueとFalseの役割を逆にすることで、「_if flags[i] is False:
_」を「_if flags[i]:
_」に変更できます。また、2番目の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 を使用しました。 (免責事項:私はメンテナの1人です。)開発バージョンは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)))
_
編集:この演習に基づいて、xmpz.bit_set(iterator)
を受け入れるようにgmpy2を変更しました。次のコードを使用すると、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:もう一度試してください! xmpz.bit_set(slice)
を受け入れるようにgmpy2を変更しました。次のコードを使用すると、1,000,000,000未満のすべての素数の実行時間は、Python 2.7とPython 3.2の両方で約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:xmpzのビットレベルでのスライスを適切にサポートするようにgmpy2を更新しました。パフォーマンスに変化はありませんが、非常に優れたAPIです。少し調整を加えたところ、約37秒に短縮されました。 (gmpy2 2.0.0b1の変更については、編集#4を参照してください。)
_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:前の例を壊すgmpy22.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:gmpy22.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))
_
OK、これが私の2番目の答えですが、速度が重要なので、 bitarray モジュールについて言及する必要があると思いました-それは bitstring の宿敵:)。これはC拡張機能であるだけでなく(純粋なPythonになることを期待しています))、スライスの割り当てもサポートしているため、この場合に最適です。=ではまだ使用できません。 Python 3ですが。
私はこれを最適化しようとさえしていません、私はビットストリングバージョンを書き直しただけです。私のマシンでは、100万未満の素数で0.16秒を取得します。
10億ドルの間、それは完全にうまく動作し、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
さて、これが私が今夜行った(ほぼ完全な)包括的なベンチマークで、どのコードが最も速く実行されるかを確認します。うまくいけば、誰かがこのリストが役に立つと思うでしょう。自分のマシンで完了するのに30秒以上かかるものはすべて省略しました。
ご意見をお寄せいただいた皆様に感謝申し上げます。私はあなたの努力から多くの洞察を得ました、そしてあなたもそうだといいのですが。
私のマシン:AMD ZM-86、2.40 Ghzデュアルコア、4GBのRAM。これはHPTouchsmartTx2ラップトップです。 Pastebinにリンクしているかもしれませんが、自分のマシンで次のすべてのベンチマークを行ったことに注意してください。
Gmpy2ベンチマークを構築できたら、追加します。
すべてのベンチマークはPython 2.6 x86
1,000,000までの素数nのリストを返す:(UsingPythonジェネレーター)
セバスチャンのnumpyジェネレータバージョン(更新) -121 ms @
マークのふるい+ホイール -154ミリ秒
スライス付きのロバートのバージョン -159ミリ秒
スライスを使用した改良版 -205ミリ秒
列挙型のNumpyジェネレーター -249 ms @
マークの基本ふるい -317ミリ秒
元のソリューションに対するcasevhの改善 -343ミリ秒
私の修正したnumpyジェネレーターソリューション -407ミリ秒
質問の元の方法 -409ミリ秒
ビット配列ソリューション -414 ms @
Pure Python with bytearray --1394 ms @
スコットのBitStringソリューション -6659 ms @
'@'は、このメソッドが私のマシンセットアップで最大n <1,000,000,000を生成できることを意味します。
さらに、ジェネレーターが不要で、リスト全体が一度に必要な場合は、次のようにします。
RosettaCodeからのnumpyソリューション -32 ms @
(numpyソリューションは最大10億を生成することもでき、61.6259秒かかりました。メモリが1回交換されたため、2倍の時間がかかったと思われます。)
これは私がしばらく前に書いたバージョンです。速度についてあなたと比較するのは興味深いかもしれません。ただし、スペースの問題については何もしません(実際、バージョンよりも悪い可能性があります)。
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
ホイールを使用したより高速なバージョンがありますが、はるかに複雑です。元のソースは ここ です。
さて、これがホイールを使ったバージョンです。 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を除く)が6を法とする1または5(== 2 * 3)に合同であることに気付くことができます。 。次のステップは、すべての素数(2、3、および5を除く)が1、7、11、13、17、19、23、29(30を法とする)のいずれかに合同であることに注意することです(ここでは30 == 2 * 3 * 5)など。
ビット文字列を使用して速度を向上させる方法の1つは、現在の数値の倍数を除外するときに「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倍の速度で実行されます。
私の他の考えは、ビット文字列を使用して奇数のみを表すことでした。次に、ジェネレータを返すflags.findall([0])
を使用して、未設定のビットを見つけることができます。それがはるかに高速であるかどうかはわかりません(ビットパターンを効率的に見つけるのはそれほど簡単ではありません)。
[完全な開示:私はビットストリングモジュールを書いたので、ここで危機に瀕していることに誇りを持っています!]
比較として、ビットストリングメソッドから内臓を取り除いて同じように実行しましたが、範囲チェックなどは行いませんでした。これにより、純粋なPython =これは10億の要素に対して機能します(アルゴリズムを変更せずに、不正行為だと思います!)
_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)
_
私のマシンでは、これは100万要素に対して約0.62秒で実行されます。これは、ビット配列の回答の約4分の1の速度であることを意味します。それは純粋なPythonにとってはかなり合理的だと思います。
検討したいオプションの1つは、C/C++モジュールをコンパイルするだけなので、ビットをいじる機能に直接アクセスできます。 AFAIKは、そのような性質のものを記述し、C/C++で実行された計算の完了時にのみ結果を返すことができます。これを入力しているので、速度のために配列をネイティブCとして格納するnumpyも見ることができます。しかし、それがビットストリングモジュールよりも速いかどうかはわかりません!
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
)そして結果をそれ自体に追加します。次に、ステップを2倍にして、次のシフトで両方のビットをコピーし、以下同様に制限に達するまで続けます。これにより、数値のすべての倍数がO(log(limit))時間の制限まで設定されます。
これは、これまでに投稿されたビット配列/ビット文字列ソリューションよりも少ないメモリと、Robert William Hanksのprimesgen()の約1/8のメモリを使用し、primesgen()よりも高速に実行されるPython3コードです(37KBのメモリを使用して1,000,000でわずかに高速です) 、34MB未満を使用して1,000,000,000でprimesgen()より3倍高速)。チャンクサイズ(コード内の可変チャンク)を増やすと、より多くのメモリが使用されますが、プログラムは制限まで高速化されます。メモリへの寄与がsieveのn // 30バイトの約10%未満になるように値を選択しました。 Will Nessの無限ジェネレーター ( https://stackoverflow.com/a/19391111/5439078 も参照)のようにメモリ効率が良くありません。終了、圧縮形式)すべての計算された素数。
平方根の計算が正確である限り、これは正しく機能するはずです(Pythonが64ビットのdoubleを使用する場合は約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(on https:// pypi.python.org/ 、primesieveを使用 http://primesieve.org/ )。
もう1つの本当にばかげたオプションですが、非常に高速に利用できる素数の大規模なリストが本当に必要な場合は、これが役立ちます。たとえば、プロジェクトオイラーの問題に答えるためのツールとしてそれらが必要な場合(問題がマインドゲームとしてコードを最適化するだけの場合は関係ありません)。
遅いソリューションを使用してリストを生成し、それをpythonソースファイルに保存します。primes.py
は次のようになります。
primes = [ a list of a million primes numbers here ]
これらを使用するには、import primes
を実行するだけで、ディスクIOの速度で最小限のメモリフットプリントでそれらを取得できます。複雑さは素数の数です:-)
最適化が不十分なソリューションを使用してこのリストを生成した場合でも、実行されるのは1回だけであり、それほど重要ではありません。
ピクルス/アンピクルを使用すると、おそらくさらに高速にすることができますが、アイデアは得られます...
セグメント化されたエラトステネスのふるいを使用できます。各セグメントに使用されたメモリは、次のセグメントに再利用されます。