С - решето Эратосфена - поле

Я собираюсь реализовать решето Эратосфена и имеют общий вопрос относительно ситового массива.

я реализовал сито довольно много раз (в C) и всегда использовал массив uint8_t (из <stdint.h>) как решето. Это довольно неэффективная память, так как для каждого числа используется 8 бит, хотя одного бита должно быть достаточно.

как бы я пошел об этом в C? Мне нужен массив битов. Я мог бы в значительной степени создайте массив любого типа (uint8_t, uint16_t, uint32_t, uint64_t) и доступ к одиночным битам с битовыми масками и т. д.

какой тип данных я должен предпочесть и какие операции я должен использовать для доступа к битам без потери производительности?

PS: Я не думаю, что это дубликат просто реализация BitArray, так как это вопрос специфичен для сита Эратосфена, так как его основная природа должна быть эффективной (не только в использовании памяти, но и в доступ.) Я подумал, что, возможно, можно использовать разные трюки, чтобы сделать процесс просеивания более эффективным...

2 ответов


как упоминал флюгер в своих комментариях, вы можете сэкономить дополнительное пространство, рассматривая только каждое другое число, так как все четные числа, кроме 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 (1 миллиард), он использует 64000000 (64 миллиона) байт памяти.

ключевые выражения следующие:

для" нормального " битового массива:

Набор бит 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 заменяется битовыми сдвигами и побитовыми-и масками, но большинство компиляторов должны делать это за вас.


самые большие собственные типы (возможно uint64_t), Как правило, выполняют лучше. Вы можете хранить свои битовые маски в массиве или генерировать их на месте с помощью bitshifting. Вопреки интуиции, генерация на месте может работать лучше из-за лучших характеристик кэширования/доступа к памяти. В любом случае, неплохо начать кодировать его довольно общим способом (например, определить макросы типов, если вы используете чистый C), а затем протестировать разные версии.

кэширование (постоянно или nonpersistently)некоторые из ваших результатов могут быть неплохой идеей.