C - Eratosthenes的筛子 - BitField

时间:2016-06-27 17:51:08

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

我即将实施Sieve of Eratosthenes并对筛子阵列有一个普遍的问题。

我现在已经实施了几次筛子(在C中),并且总是使用uint8_t<stdint.h>之外的)数组作为筛子。这是非常低效的内存,因为每个数字都使用8位来筛选,即使一位应该足够。

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

我应该选择哪种数据类型以及在没有性能损失的情况下应该使用哪些操作来访问这些位?

PS:我不认为这是只是 BitArray实现的重复,因为它的问题是关于Eratosthenes筛选的,因为它的主要性质需要高效(不仅仅是在内存使用方面,但在访问中)。我在想,也许可以使用不同的技巧来使筛分过程更有效......

2 个答案:

答案 0 :(得分:3)

正如Weather Vane在评论中提到的那样,你可以通过仅考虑其他所有数字来节省额外的空间,因为除了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万)字节的内存。

关键表达式如下:

对于&#34;正常&#34;位数组:

设置位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的幂的除法和模数被位移和按位AND掩码替换,但大多数编译器应该为你做。

答案 1 :(得分:2)

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

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