通过递归公式改进纯 Python 的素数筛选器
我正在尝试进一步优化在质数线程中的最佳解决方案,主要是想去掉计算子列表长度的复杂公式。使用len()来获取同一个子序列的长度太慢了,因为len()的计算成本高,而且生成子序列的过程也很耗时。这样做似乎能稍微加快函数的运行速度,但我还没能去掉除法,虽然我只在条件语句中使用了除法。当然,我可以尝试通过优化起始标记来简化长度计算,而不是用n*n的方式...
我把除法符号/换成了整数除法//,这样就能和Python 3兼容了。
from __future__ import division
另外,我很好奇这个递归公式是否能帮助加速numpy的解决方案,但我对使用numpy的经验不多。
如果你为代码启用psyco,那么情况就完全不同了,Atkins筛法的代码会比这种特殊的切片技术更快。
import cProfile
def rwh_primes1(n):
# http://stackoverflow.com/questions/2068372/fastest-way-to-list-all-primes-below-n-in-python/3035188#3035188
""" Returns a list of primes < n """
sieve = [True] * (n//2)
for i in xrange(3,int(n**0.5)+1,2):
if sieve[i//2]:
sieve[i*i//2::i] = [False] * ((n-i*i-1)//(2*i)+1)
return [2] + [2*i+1 for i in xrange(1,n/2) if sieve[i]]
def primes(n):
# http://stackoverflow.com/questions/2068372/fastest-way-to-list-all-primes-below-n-in-python/3035188#3035188
# recurrence formula for length by amount1 and amount2 Tony Veijalainen 2010
""" Returns a list of primes < n """
sieve = [True] * (n//2)
amount1 = n-10
amount2 = 6
for i in xrange(3,int(n**0.5)+1,2):
if sieve[i//2]:
## can you make recurrence formula for whole reciprocal?
sieve[i*i//2::i] = [False] * (amount1//amount2+1)
amount1-=4*i+4
amount2+=4
return [2] + [2*i+1 for i in xrange(1,n//2) if sieve[i]]
numprimes=1000000
print('Profiling')
cProfile.Profile.bias = 4e-6
for test in (rwh_primes1, primes):
cProfile.run("test(numprimes)")
性能分析(不同版本之间差别不大)
3 function calls in 0.191 CPU seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.006 0.006 0.191 0.191 <string>:1(<module>)
1 0.185 0.185 0.185 0.185 myprimes.py:3(rwh_primes1)
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
3 function calls in 0.192 CPU seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.006 0.006 0.192 0.192 <string>:1(<module>)
1 0.186 0.186 0.186 0.186 myprimes.py:12(primes)
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
有趣的是,当我把限制提高到10**8,并给函数加上计时装饰器,去掉性能分析时:
rwh_primes1 took 23.670 s
primes took 22.792 s
primesieve took 10.850 s
有趣的是,如果你不生成质数列表,而是直接返回筛法本身,所需的时间大约是生成质数列表版本的一半。
2 个回答
0
如果你决定要用C++来提高速度,我把Python的筛法移植到了C++。详细讨论可以在这里找到:将优化过的埃拉托斯特尼筛法从Python移植到C++。
在Intel Q6600处理器上,使用Ubuntu 10.10系统,编译时用的命令是g++ -O3
,当N设为100000000时,运行时间是415毫秒。
#include <vector>
#include <boost/dynamic_bitset.hpp>
// http://vault.embedded.com/98/9802fe2.htm - integer square root
unsigned short isqrt(unsigned long a) {
unsigned long rem = 0;
unsigned long root = 0;
for (short i = 0; i < 16; i++) {
root <<= 1;
rem = ((rem << 2) + (a >> 30));
a <<= 2;
root++;
if (root <= rem) {
rem -= root;
root++;
} else root--;
}
return static_cast<unsigned short> (root >> 1);
}
// https://stackoverflow.com/questions/2068372/fastest-way-to-list-all-primes-below-n-in-python/3035188#3035188
// https://stackoverflow.com/questions/5293238/porting-optimized-sieve-of-eratosthenes-from-python-to-c/5293492
template <class T>
void primesbelow(T N, std::vector<T> &primes) {
T i, j, k, sievemax, sievemaxroot;
sievemax = N/3;
if ((N % 6) == 2) sievemax++;
sievemaxroot = isqrt(N)/3;
boost::dynamic_bitset<> sieve(sievemax);
sieve.set();
sieve[0] = 0;
for (i = 0; i <= sievemaxroot; i++) {
if (sieve[i]) {
k = (3*i + 1) | 1;
for (j = k*k/3; j < sievemax; j += 2*k) sieve[j] = 0;
for (j = (k*k+4*k-2*k*(i&1))/3; j < sievemax; j += 2*k) sieve[j] = 0;
}
}
primes.push_back(2);
primes.push_back(3);
for (i = 0; i < sievemax; i++) {
if (sieve[i]) primes.push_back((3*i+1)|1);
}
}
1
你可以进行一种轮子优化。因为2和3的倍数都不是质数,所以根本不需要存储它们。接下来,你可以从5开始,跳过2和3的倍数,按照2、4、2、4、2、4这样的步长来增加。
下面是一个C++的代码示例。希望这对你有帮助。
void sieve23()
{
int lim=sqrt(MAX);
for(int i=5,bit1=0;i<=lim;i+=(bit1?4:2),bit1^=1)
{
if(!isComp[i/3])
{
for(int j=i,bit2=1;;)
{
j+=(bit2?4*i:2*i);
bit2=!bit2;
if(j>=MAX)break;
isComp[j/3]=1;
}
}
}
}