MAX
(\ $ 10 ^ 9 \ $)的1000000000
值,大约需要45秒才能到达打印“完成”的行。如何加快速度?在屏幕上打印总是要花很多时间吗?我知道它是硬件。
将MAX的大小增加到\ $ 10 ^ 9 \ $以上,在
memset(a, true, MAX);
这行中给了我一个例外。此功能有限制吗?我应该能够使用所有RAM。运行此程序将使用大约954 MB的内存。void sieve_of_eratosthenes(){
bool* a;
a = (bool*)malloc(MAX * sizeof(bool));
memset(a, true, MAX);
unsigned long int i = 1;
while (i < MAX){
while (((++i)<MAX)&&(!a[i]));
if (2 * i >= MAX)
break;
for (unsigned long int k = 2 * i; k < MAX; k += i)
if (a[k])
a[k] = false;
}
std::cout << "done\n";
for (unsigned long int i = 2; i < MAX; i++)
if (a[i])
std::cout << i << "\n";
getchar(); getchar();
}
#1 楼
有很多方法可以使运行速度更快。首先,通过仅将奇数存储在筛子中来节省大量空间。唯一的偶数是2。请注意,在现代计算机上,算法花费的时间大致等于其读取和写入的数据量,因此将所需空间减半将使执行时间减半。因此索引\ $ i \ $的布尔值表示数字\ $ 2_i + 1 \ $。
现在您只需要删除奇数即可。最初将所有内容都设置为true时,因为1不是素数,所以将
array[0]
设置为false。现在,当您删除素数p的倍数时,您会知道所有小于\ $ p ^ 2 \ $的倍数都已被删除,因此您开始在\ $ p ^ 2 \ $处删除素数的循环。并且您一次增加2p,因为p*p
,(p+2)*p
和(p+4)*p
等是p的奇数倍。并且由于删除的第一个素数是\ $ p ^ 2 \ $,因此当\ $ p ^ 2 \ $ <= MAX时,可以停止循环查找素数。 现在情况有些棘手:您正在寻找下一个质数p。为此,您检查一个
[i]
,直到找到一个正确的。索引i映射到素数p = 2i + 1
。 \ $ p ^ 2 \ $是一个奇数,数字\ $ p ^ 2 \ $存储在索引j = (p^2 - 1) / 2
。因此,请清除数字[j]
,[j + p]
等。到目前为止,这很简单,现在我们变得更聪明了。您的
MAX
很大,例如\ $ 10 ^ 9 \ $。当您删除3的倍数时,您将得到一个重复的模式。对于p = 1、3、5、7、9、11、13、15、17的数字,您将获得模式(true,false,true),(true,false,true),(true,false,true)等等。三个值永远重复。用该模式填充前15个数字,然后删除5的倍数:删除后从(T,F,T,T,F,T,T,F,T,T,F,T,T,F,T)您保留的5的倍数(T,F,F,T,F,T,T,F,T,T,F,T,F,F,T)。十五个布尔型的这种模式将永远重复。您制作了7个副本,给出了105个数字,并删除了7的倍数。您制作了11个副本,给出了1155个数字,并删除了11的倍数。您制作了13个副本,给出了15,015个数字,并删除了13的倍数。您可能制作了17个副本,给出了255,255个数字。并删除17的倍数。这非常快,因为您没有十亿个数字,只有几十万个数字。完成后,使用memcpy
将数据复制到整个数组中。对于最后一部分,您需要注意不要覆盖数组的末尾。那只是六个数字,但是这六个数字在工作中起着非常重要的作用!对于其他质数,您将删除所有奇数倍数。我们可以更快地做到这一点。取p =101。将删除101p,103p,105p,107p等。但是105p可被3整除,因此已被删除。同样适用于111p,依此类推。因此,您可以执行以下操作:删除\ $ p ^ 2 \ $。如果
p+2
不能被3整除,则还要删除(p+2)*p
。您要删除的下一个数字将是3的倍数。因此,您不必将每次删除的数字增加2p,而是增加4p(避免使用3的倍数),然后增加2p,然后再增加4p,2p ,4p,2p等。这样可以节省三分之一的工作。另一项改进:您只使用一位,而不用一个字节来存储每个数字。这意味着您需要执行移位操作才能访问每个位,但是使用的内存量减少了1倍。这使您可以处理更大的数字,而更少的内存将更快地工作。
最后一个大问题:优化内存访问。假设您有MAX = 10亿,并且每个奇数= 62.5兆字节使用一位。这远远超过了您的处理器缓存。假设您有2MB的L3缓存= 3200万个数字。在这种情况下,您将对前3200万个数字完全执行筛选操作。因为您的数据全部在L3缓存中,所以运行速度会更快。假设您有256KB二级缓存= 400万个数字。在这种情况下,您将对前400万个数字完全执行筛选操作,然后对接下来的400万个数字执行筛选操作,依此类推。这甚至更快,因为现在您使用的所有数据都在二级缓存中,并且可以非常快速地读取/写入。
#2 楼
您可以做一些事情:使用位向量(带有位操作)代替布尔数组。这样可以为您提供8倍的内存。如果使用C ++
vector< bool>
,则可以免费获得此优化。在数组中仅存储奇数。这将在内存中节省2的额外系数。您需要稍微调整程序的逻辑,但这并不是非常困难。这也节省了一些时间,因为在第一次迭代中,算法仅标记了一半的数字。
外循环(
while (i < MAX)
)只需从0到ceil(sqrt(MAX))
即可运行。内循环可以从
i*i
开始不只是2*i
。总结前两个将为您提供16倍的内存(或给定内存可筛选的16个数字),而后两个将显着提高运行时间(不请确定精确到多少。)段错误很可能是由于
a == NULL
(即malloc
例程无法分配内存)这一事实造成的。您需要在NULL
调用之后和malloc
之前检查memset
。评论
\ $ \ begingroup \ $
可能值得注意的是C ++具有一个std :: bitset类,该类基本上等效于std :: vector
\ $ \ endgroup \ $
–wil93
2015年12月20日23:33
#3 楼
尽管最好在C ++中使用容器类,而不是手动分配内存,但我仍要指出这一点,因为仍然应该这样说:a = (bool*)malloc(MAX * sizeof(bool));
在C ++中,最好使用
new
而不是malloc()
。这也将避免需要这种强制转换,而C ++不需要这种强制转换。您还可以初始化
a
,而不是先声明然后分配给它。bool* a = new bool[MAX];
但是您仍然应该使用容器类,如@Andreas H所述。此外,您从未在这里使用
free()
,因此此函数将导致内存泄漏。评论
\ $ \ begingroup \ $
bool * a = new bool [MAX];不允许我的数组大小超过10 ^ 8。我使用了vector
\ $ \ endgroup \ $
– piepi
2015年12月19日在22:03
\ $ \ begingroup \ $
@piepi:容器类(例如std :: vector)(带有int而不是bool)如何处理?在返回之前,应在函数末尾调用free()。
\ $ \ endgroup \ $
– Jamal♦
2015年12月19日在22:05
\ $ \ begingroup \ $
你不认为int会比bool占用更大的空间吗?
\ $ \ endgroup \ $
– piepi
15年12月19日在22:29
\ $ \ begingroup \ $
@piepi:也许吧,但实际上std :: vector
\ $ \ endgroup \ $
– Jamal♦
15年12月19日在22:35
\ $ \ begingroup \ $
为了有效利用内存,我正在考虑只使用1位变量。那可能吗?我在SO上读到,这样做可能不会很有效,因为它可能导致处理器可以轻松地处理打包的数据,而不是在涉及打包和拆包时?他们比位var更加亲智或更加愚蠢。
\ $ \ endgroup \ $
– piepi
15年12月19日在22:40
#4 楼
您是否有某些原因需要使用筛子?我还没有尝试使用建议的压缩方法来减少内存使用,但是简单测试直到平方根路由的所有内容,都会使您正在使用的Sieve的实现不可行。一旦开始导致缓存丢失,就无法筛分Sieve的性能,即使使用位打包,也无法将其保留在L1缓存中。评论
\ $ \ begingroup \ $
快速分段筛网几乎总是使用适合L1缓存的分段。如果范围较大(远远超过2 ^ 32),则困难在于记住/管理每个素点从一个片段到另一个片段的工作偏移量(相位)。对于任何给定的段,在该段中出现倍数的质数的数量只是质数的极小部分,直到段上限的平方根,因此花了大量的时间在“无趣”上进行迭代素数。接近2 ^ 64的32KB段仍仅包含32K奇数,但有2.03亿个主要因子可能适用。
\ $ \ endgroup \ $
– DarthGizka
2015年12月20日9:39
\ $ \ begingroup \ $
因此,诀窍是使用存储分区,以便可以将给定的工作偏移量“发布”到将再次使用或至少接近该偏移量的段中,以使该偏移量(或更确切地说是其质数)在中间段上工作时不必考虑。血腥细节可以在primesieve.org上阅读。
\ $ \ endgroup \ $
– DarthGizka
15年12月20日在9:43
\ $ \ begingroup \ $
@DarthGizka嗯? “ primesieve生成的质数比Eratosthenes实现的普通C / C ++筛网快约50倍(单线程),比试验部门快约10,000倍。”这就意味着试验部门比朴素的筛子方法要慢200倍-但是当我尝试时,我发现试验部门至少在我尝试的范围内就从水中吹出了朴素的筛子。
\ $ \ endgroup \ $
–Loren Pechtel
15年12月20日在22:18
\ $ \ begingroup \ $
筛子用于回答重复的查询或批量生成素数。如果您只需要测试一个或几个极小的素数,那么Trial Division必须始终更快(尤其是仅前十个较小的素数就可以发现超过85%的复合材料)。原因是筛子必须处理所有素数直到平方根才可以开始回答查询,而一旦证明了候选人的综合能力,审判分庭就可以停止。无论如何,我只是在解决您有关缓存的问题,并告诉您真正的问题在哪里。
\ $ \ endgroup \ $
– DarthGizka
2015年12月21日在6:44
\ $ \ begingroup \ $
@DarthGizka我正在像筛子一样批量加工它们。我忘了我走了多高,但筛子却掉得很厉害。
\ $ \ endgroup \ $
–Loren Pechtel
15年12月21日在19:37
评论
要支持大量,请使用Eratosthenes的分段筛,请参阅primesieve。如何在没有Mathjax的情况下加快对素数的搜索!哈哈GL。
我已回滚上一次编辑。收到答案后,请查看您可能会做什么和可能不会做什么。如果您希望对新版本进行审核,则可以提出一个新问题,然后只需链接回该原始版本即可。
@AlecTeal实际上...代码审查确实启用了mathjax :) \ $可以解决问题
您是否正在为32位模式进行编译?在32位进程中的内存分配中,大约有950MB的大小限制。使用64位ISA的主要原因之一是要处理更多的内存。 32位x86已经过时很长时间了。另外,使用calloc来获取已经归零的内存,并将筛子设置为true。这样就可以避免代价高昂的内存集。