关于算法:如何优化erathesthenes的筛选器,以仅存储很大范围内的质数?

How can I optimize sieve of eratosthenes to just store prime numbers for a very large range?

我研究了Eratosthenes筛子的工作原理,该方法使用迭代生成质数直至给定数并剔除所有复合数。 而且算法只需迭代到sqrt(n),其中n是我们需要找到所有素数的上限。 我们知道,与复合数相比,最多n = 10 ^ 9的质数数量要少得多。 因此,我们将所有空间都标记为复合,以仅表示这些数字不是素数。
我的问题是,由于我们处理的范围很大(因为质数非常少),我们是否可以将算法修改为仅存储质数?
我们可以直接存储质数吗?


将结构从一组(筛子)的结构(每个候选位一位)更改为存储素数(例如,在列表,向量或树结构中)实际上增加了存储需求。

好。

例如:在2 ^ 32以下有203.280.221个素数。大小为uint32_t的数组大约需要775 MiB,而相应的位图(也称为集合表示)仅占用512 MiB(2 ^ 32位/ 8位/字节= 2 ^ 29字节)。

好。

具有固定像元大小的最紧凑的基于数字的表示形式将存储连续奇数素数之间的对等距离,因为直到约2 ^ 40,对等距离才适合一个字节。对于素数最多为2 ^ 32的193 MiB,这比仅包含赔率的位图要小一些,但仅对顺序处理有效。对于筛选而言,这是不合适的,因为正如Anatolijs所指出的那样,诸如Eratosthenes的Sieve之类的算法实际上需要集合表示。

好。

通过省略小质数的倍数,可以将位图大大缩小。最著名的是仅赔率表示法,省略了数字2及其倍数。这样就可以将空间需求减少一半,达到256 MiB,而几乎不会增加代码复杂性。您只需要记住在需要时将数字2凭空拉出,因为它没有出现在筛子中。

好。

通过省去更多小质数的倍数,可以节省更多空间。这种"仅奇数"技巧的概括通常称为"轮式存储"(请参阅??Wikipedia中的"轮式分解")。但是,向车轮添加更多的小质数得到的收益越来越小,而车轮模量("周长")急剧增加。加3会删除剩余数字的1/3,加5会再减去1/5,加7只会使您再1/7,依此类推。

好。

这是向轮子添加另一个质数可以使您得到什么的概述。"比率"是轮式/约简集相对于代表每个数字的全套的大小;与上一步相比,"增量"给出了收缩率。"辐条"是指需要表示/存储的主要辐条的数量;轮辐的总数当然等于其模数(周长)。

好。

wheel efficiency table

好。

mod 30车轮(对于2 ^ 32以下的塑料,其轮毂约为136 MiB)具有出色的成本/收益比,因为它具有八个带轴承的辐条,这意味着车轮和8轮之间是一对一的对应关系。位字节。这实现了许多有效的实现技巧。但是,尽管有这种偶然的情况,但它在增加代码复杂性方面的成本却是相当可观的,并且对于许多用途而言,仅赔率筛子(" mod 2 wheel"(mod 2 wheel))提供了迄今为止最大的收益。

好。

还有两个注意事项值得牢记。首先是这样的数据大小通常会大大超出内存缓存的容量,因此程序通常会花费大量时间等待内存系统交付数据。典型的筛选方式(遍及整个范围,一次又一次地遍历)使情况更加复杂。通过小批量处理适合处理器的1级数据高速缓存(通常为32 KiB)的数据,可以实现几个数量级的加速。通过保持在L2和L3高速缓存的容量之内(分别为几百个KiB和几个MiB),仍然可以实现较小的加速。此处的关键字是"分段筛分"。

好。

第二个考虑因素是,许多筛选任务(例如著名的SPOJ PRIME1及其更新版本的PRINT(具有扩展的范围和更严格的时间限制))仅需要达到上限平方根的小素数即可永久用于直接访问。这是一个相对较小的数字:与PRINT一样,在筛选2 ^ 31时为3512。

好。

由于这些素数已经过筛,因此不再需要集合表示形式,并且由于它们很少,因此存储空间没有问题。这意味着将它们最有效地保留为向量或列表中的实际数字,以便于进行迭代,并可能附带其他辅助数据,例如当前的工作偏移量和相位。然后可以通过称为"窗口筛分"的技术轻松完成实际的筛分任务。在PRIME1和PRINT的情况下,这比筛分整个范围至上限要快几个数量级,因为这两个任务都只需要筛分少量的子范围。

好。

好。


您可以执行此操作(从数组/链接列表中删除检测为非质数的数字),但是算法的时间复杂度将降低为O(N ^ 2 / log(N))或类似的值,而不是原始O(N * log(N))。这是因为您将不能说"数字2X,3X,4X,..."不再是质数。您将不得不遍历整个压缩列表。


您可以仅通过"存储"奇数来将筛子尺寸减半。这要求代码显式处理测试偶数的情况。对于奇数,筛子的位b代表n = 2b +3。因此位0代表3,位1代表5,依此类推。在数字n和位索引b之间进行转换的开销很小。

此技术对您是否有用,取决于您所需的内存/速度平衡。


如果您已经研究过筛网,您必须知道我们没有开始的质数。我们有一个array,sizeof等于范围。现在,如果您希望范围为10e9,则希望它为数组的大小。您没有提到任何语言,但是对于每个数字,您都需要一个bit来表示它是否为质数。

即使那样也意味着您需要大于100 MB RAM的10^9 bits = 1.125 * 10^8个字节。

假设您拥有所有这些,最优化的筛网将花费O(n * log(log n))时间,如果在每秒评估10e8条指令的机器上,如果n = 10e9仍将花费几分钟。

现在,假设您拥有所有这些,直到10e9q = 50,847,534的素数数量仍然保存,仍然需要q * 4 bytes,它仍然大于100MB。 (更多RAM)

即使您删除的索引是2、3或5的倍数,也会删除21 numbers in every 30。这还不够好,因为总的来说,您仍然需要大约140 MB空间。 (40MB = 10 ^ 9位的三分之一+ 100MB用于存储质数)。

因此,由于为了存储素数,在任何情况下您都将需要类似数量的内存(与计算顺序相同),因此您的问题就没有IMO了。


一旦证明它是合成的,就可以从数组/向量中删除每个合成的数字。或者,当您填充要通过筛子的数字数组时,请删除所有偶数(除2以外)和所有以5结尾的数字。