【问题标题】:C - Sieve of Eratosthenes - BitFieldC - 埃拉托色尼筛 - BitField
【发布时间】:2016-10-29 20:04:39
【问题描述】:

我即将实施Sieve of Eratosthenes 并且有一个关于筛阵列的一般性问题。

我现在已经多次(在 C 中)实现了筛子,并且总是使用 uint8_t 的数组(在 <stdint.h> 中)作为筛子。这是相当低的内存效率,因为每个数字都使用 8 位进行筛选,即使一位应该就足够了。

我将如何在 C 中解决这个问题?我需要一个位数组。我几乎可以创建任何类型的数组(uint8_tuint16_tuint32_tuint64_t)并使用位掩码等访问单个位。

我应该更喜欢什么数据类型以及我应该使用什么操作来访问这些位而不损失性能?

PS:我不认为这是 just BitArray 实现的副本,因为它的问题是关于 Eratosthenes 筛的具体问题,因为它的主要性质需要高效(不仅在内存使用中,但在访问中)。我在想,也许可以使用不同的技巧来提高筛分过程的效率......

【问题讨论】:

  • 我会说与 Eratosthenes 筛子的有效实现的链接足以使这个 not 与另一个问题重复。我觉得这是一个有趣的问题,应该重新讨论。
  • 可以通过忽略筛子中的偶数(因为只有2是素数)来提高内存效率,所以8位可以代表16个数字的状态。
  • @JohnColeman 提到筛子几乎无关紧要:如果第一段被删除,这个问题仍然有意义(稍作修改)。即使它不是重复的,它也应该作为 Too Broad 或 Off Topic 关闭(没有代码,没有针对实际问题显示的研究工作)。
  • 在问题编辑之后,可能在 CodeReview 上 - 使用代码 ;)
  • @WeatherVane 我也在考虑这个问题。谢谢 :) 我认为通过一些巧妙的技巧,也可以留下 3 和 5 的倍数。问题是在哪里停止/技巧变得比实际计算更复杂/更难计算。

标签: c arrays bit-fields sieve-of-eratosthenes sieve


【解决方案1】:

正如 Weather Vane 在他的 cmets 中所提到的,您可以通过仅考虑每隔一个数字来节省额外的空间,因为除了 2 之外的所有偶数都是非质数。

所以在你的位数组中,每个位代表一个奇数。

这是我几年前使用这种技术完成的一个实现。

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <time.h>
#include <math.h>
#include <stdint.h>

uint8_t *num;
int count = 0;
FILE *primefile;

int main(int argc, char *argv[])
{
  int i,j,root;
  time_t t;

  if (argc>1) count=atoi(argv[1]);
  if (count < 100) {
    fprintf(stderr,"Invalid number\n");
    exit(1);
  }
  if ((num=calloc(count/16,1))==NULL) {
    perror("calloc failed");
    exit(1);
  }
  if ((primefile=fopen("primes.dat","w"))==NULL) {
    perror("Coundn't open primes.dat");
    exit(1);
  }
  t=time(NULL);
  printf("Start:\t%s",ctime(&t));
  root=floor(sqrt(count));
  // write 2 to the output file
  i=2;
  if (fwrite(&i,sizeof(i),1,primefile)==0) {
    perror("Couldn't write to primes.dat");
  }
  // process larger numbers
  for (i=3;i<count;i+=2) {
    if ((num[i>>4] & (1<<((i>>1)&7)))!=0) continue;
    if (fwrite(&i,sizeof(i),1,primefile)==0) {
      perror("Couldn't write to primes.dat");
    }
    if (i<root) {
      for (j=3*i;j<count;j+=2*i) {
        num[j>>4]|=(1<<((j>>1)&7));
      }
    }
  }
  t=time(NULL);
  printf("End:\t%s",ctime(&t));
  fclose(primefile);
  return 0;
}

这里,num 是位数组,根据搜索的上限动态分配。因此,如果您要查找最大为 1000000000(10 亿)的所有素数,它会使用 64000000(6400 万)字节的内存。

关键表达式如下:

对于“正常”位数​​组:

设置位i:

num[i>>3] |= (1<<(i&7);
// same as num[i/8] |= (1<<((i%8));

校验位i:

(num[i>>3] & (1<<(i&7))) != 0
// same as (num[i/8] & (1<<(i%8))) != 0

由于我们只跟踪所有其他数字,因此我们将 i 除以 2(或等价地,右移 1:

num[i>>4] |= (1<<((i>>1)&7);
// same as num[(i/2)/8] |= (1<<(((i/2)%8));

(num[i>>4] & (1<<((i>>1)&7))) != 0
// same as (num[(i/2)/8] & (1<<((i/2)%8))) != 0

在上面的代码中,有一些微优化,其中除以 2 的幂的除法和模数被移位和按位与掩码替换,但大多数编译器应该为您这样做。

【讨论】:

  • 非常感谢您的回答。我会在实施筛子后报告。
  • if ((num=calloc(count/16,1))==NULL) { 是个问题。示例:count == 4,则没有分配可用内存,但调用了 if ((num[0&gt;&gt;4]
  • @chux 很好。更改了输入验证,因此 count 至少为 100。
  • 我刚刚注意到的一件小事:我认为应该使用检查位 i (num[i&gt;&gt;3] &amp; (1&lt;&lt;(i&amp;7)) != 0 - 注意括号,因为 != 的先例高于 &amp;。 PS:同样在 Set bit i 你错过了关闭一些括号。
  • @Matthias 是的。这些括号在完整代码中,但不在提取的示例中。固定。
【解决方案2】:

最大的原生类型(可能是uint64_t)往往表现最好。 您可以将位掩码存储在数组中或通过位移在现场生成它们。与直觉相反,由于更好的缓存/内存访问特性,现场生成可能会表现得更好。 在任何情况下,最好以相当通用的方式开始编写代码(例如,如果您使用纯 C,则定义您的类型宏)然后测试不同的版本。

缓存(持久性或非持久性)您的一些结果也可能不是一个坏主意。

【讨论】:

  • 谢谢!我会在接下来的几天内报告我的实施情况。
猜你喜欢
  • 1970-01-01
  • 2011-12-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多