Сгенерировать простые числа от 1 до n, сбой для n > 300 миллионов

Любые предложения относительно того, как я могу заставить эту программу работать на n = 1 триллион (помимо обновления/покупки нового компьютера)?

Ошибка следующая: после сборки программа выполняется (выскакивает окно вывода в стиле командной строки) и затем быстро закрывается, и я получаю следующую ошибку "ProjectPrimes.exe прекратил работу (Windows ищет решение для эта проблема." Я подозреваю, что это связано с проблемами памяти, поскольку я впервые столкнулся с этим при n = 20 миллионов, но это было до того, как я выбрал malloc/освобождение массива "решето" (т.е. мой массив "решето" - это большой массив размерами nx 1 и с каждым элементом, состоящим из 1 или 0).

Программа обрабатывает первые 300 миллионов целых чисел (16 252 325 простых чисел) примерно за 35 секунд, так что это нормально, но ничего особенного. Как я уже упоминал, цель состоит в том, чтобы иметь возможность генерировать простые числа ниже 1 триллиона, так что я далеко...

Если уместно, вот характеристики моей машины (на случай, если цель окажется необоснованной на этой машине): 2,40 ГГц i5, 4 ГБ ОЗУ, 64-битная Windows 7.

Обзор методологии, для тех, кто не знаком: мы используем подход решета Сундарама. Не вдаваясь в доказательство, мы сначала удалим все нечетные непростые числа ниже целого числа «n», используя функцию решета: [2*(i+j+2*i*j)+1 | i ‹- [1..n/2], j ‹- [i..оптимизированная верхняя граница]]. Затем мы вычеркиваем четные числа (кроме двух, конечно). Что оставляет нас с простыми числами.

Почему функция простого числа возвращает (указатель на массив, содержащий) полный набор простых чисел меньше n? Что ж, цель состоит в том, чтобы иметь возможность идентифицировать как (i) количество простых чисел меньше n, так и (ii) перечислить простые числа меньше n. Именно поэтому я решил передать в качестве аргумента указатель на количество простых чисел меньше n.

Вот не очень интересная «основная» функция:

int main() {
    long ceiling = 300*1000*1000;
    long *numPrimes;
    long *primes;

    primes = primesToSS(ceiling+1, numPrimes);
    printf("\n\nThere are %d primes below %d.\n\n",*numPrimes,ceiling);

    free(primes);
    return 0;
}

А вот и мясо:

//n represents the ceiling, i.e., the integer below which we will generate primes
//cnt* is a pointer which will point the number of primes below n

long* primesToSS( long n, long* cnt ) {

    //initialize sieve by setting all elements equal to 1 (except for 0 and 1)
    long *sieve = malloc(n*sizeof(long));
    initArray(sieve, n, 1);
    sieve[0] = 0; sieve[1] = 0;

    //eliminate all odd composite numbers
    for (int i = 1; i <= n/2; ++i)
        for (int j = i; j <= (n-2*i)/(2*(2*i+1)); ++j)
            sieve[ 2*(i+j+2*i*j)+1 ] = 0;

    //eliminate all even numbers greater than two 
    //and count total number of primes below n
    long numPrimes = 1;
    for (int i = 3; i < n; ++i) {
        if (i % 2 == 0) sieve[i] = 0;
        numPrimes += sieve[i];
    }
    *cnt = numPrimes;

    //create array of primes
    long *primes = malloc(numPrimes*sizeof(int));
    long counter = 0;
    for (int i = 0; i < n; ++i) {
        if (sieve[i] == 1) {
            primes[counter] = i;
            counter++; } 
    }
    free(sieve);
    return primes;
}

void initArray( int* arr, int len, int n ) {
    for( int i = 0; i < len; ++i) arr[i] = n; }

person iceman    schedule 06.10.2014    source источник
comment
Какая ошибка/исключение возникает при сбое кода?   -  person Fumu 7    schedule 06.10.2014
comment
Пожалуйста, попробуйте предоставить минимальный, компилируемый, рабочий пример, который все еще показывает проблему. Помимо этого, очевидно, что malloc(n*sizeof(long)) вернет NULL для достаточно большого n.   -  person Thomas Padron-McCarthy    schedule 06.10.2014
comment
Я сомневаюсь, что вы сможете malloc триллиона длинных, если у вас нет 8 терабайт оперативной памяти   -  person Atuos    schedule 06.10.2014
comment
Разве ваш массив sieve не является массивом флагов, то есть нулем или единицей? Зачем использовать long для хранения одного бита?   -  person Thomas Padron-McCarthy    schedule 06.10.2014
comment
С памятью malloc-king такого размера у вас должно быть доступно c.a. 1 ГБ свободной И непрерывной памяти, чтобы правильно ее распределить. Вы проверяли, возвращается ли нуль от malloc? Нет? Тогда сделайте это и убедитесь сами, если это проблема.   -  person zubergu    schedule 06.10.2014
comment
Указатель numPrimes не инициализируется, когда вы передаете его primesToSS(). Внутри этой функции строка *cnt = numPrimes; будет записываться в случайное место в памяти.   -  person Blastfurnace    schedule 06.10.2014
comment
Извините за то, что изначально опубликовал некомпилируемый код, код теперь обновлен, чтобы включить функцию initArray, которая ранее отсутствовала. Кроме того, я подробно рассказал о характере ошибки, как и просили. Сейчас читаю/обрабатываю ваши комментарии. Еще раз всем спасибо   -  person iceman    schedule 06.10.2014
comment
@DipakC: пока вы не проверяете значение, возвращаемое malloc, ваш вопрос довольно неинтересен.   -  person Mat    schedule 06.10.2014
comment
Разве ваш массив сита не является массивом флагов, то есть нулем или единицей? Зачем использовать long для хранения одного бита? @ThomasP-C facepalm отличный улов. редактировать: обновить. на удивление, особого эффекта это не дало (350 млн против 300 млн)...   -  person iceman    schedule 06.10.2014
comment
Вы должны использовать %ld в своих форматах для печати long значений. Также кажется немного чрезмерным использование 64-битных значений long для хранения значений 0 или 1. Если вы используете отдельные биты, вы сможете хранить гораздо больше значений в сите. Вы также можете подумать о том, чтобы вообще не беспокоиться о хранении четных чисел; массив битов может просто кодировать нечетные числа (но это позволяет вам получить 128 чисел в каждый элемент массива, где в настоящее время вы получаете только 1).   -  person Jonathan Leffler    schedule 06.10.2014
comment
@JonathanLeffler Как я заметил в своем ответе, ОП волей-неволей смешивает int и long.   -  person Jim Balter    schedule 06.10.2014
comment
@JimBalter: да, но ты не пропинговал это конкретно.   -  person Jonathan Leffler    schedule 06.10.2014
comment
Я не могу понять, почему массив long, а ваше сито требует только двух видов информации - да или нет. Я бы использовал, например, uint8_t - здесь можно хранить 8 бит информации.   -  person soerium    schedule 06.10.2014
comment
@JonathanLeffler Я не говорил иначе ... Я просто отметил, что ошибка, на которую вы указали (и я не уловил), является одной из нескольких одинаковых. Я подозреваю, что OP начал с int и изменил его на long в попытке обработать более крупные простые числа, но не выполнил полную или тщательную работу.   -  person Jim Balter    schedule 06.10.2014
comment
numPrimes является одновременно указателем указывающим ни на что (в main()) и длинным целым числом (в другом месте)   -  person joop    schedule 06.10.2014


Ответы (3)


Сделаем некоторые наблюдения:

  • Как люди упоминали в комментариях и ответах, вам нужен только 1 бит, чтобы сохранить, является ли число простым. Таким образом, вы можете упаковать данные для 8 номеров в 1 байт. Реализуйте собственную структуру данных битового набора, чтобы сделать код чище.
  • Также упоминается в комментариях, поскольку все простые числа больше 2 являются нечетными числами, нет необходимости хранить данные для четных чисел. Это позволяет упаковать 16 чисел в 1 байт.
  • Следуя идее факторизации колеса, вы можете дополнительно упаковать 24 числа в 1 байт, если вы храните только биты для чисел, конгруэнтных 1 или 5 по модулю 6. Более высокое значение по модулю сэкономит немного больше места (с уменьшением отдачи), но логика доступа к структуре данных битового набора может замедлить алгоритм.
  • Чтобы программа работала с 1 триллионом (1012) чисел, как вы просеиваете, вам нужно всего лишь собрать список простых чисел меньше 1 миллиона (106) . В больших числах нет необходимости, поскольку другой множитель составных чисел меньше 1 триллиона уже был бы охвачен списком.
  • Упаковывая числа в 16 чисел/байт (самая базовая настройка, которую вы должны сделать), вам потребуется ~ 58 ГБ ОЗУ. Однако нет необходимости выделять весь диапазон. Просто выделите меньший диапазон от нескольких сотен миллионов до нескольких миллиардов чисел (попробуйте изменить число для максимальной производительности), что соответствует не более чем нескольким сотням МБ, а затем используйте список простых чисел менее 1 миллиона для просеивания. диапазоны.

    Этот шаг можно сделать параллельно — вам нужно только поделиться списком простых чисел меньше 1 миллиона.

    Кстати, этот прием называется сегментным ситом.

person nhahtdh    schedule 06.10.2014
comment
Хотите объяснить 4-й шаг? Насколько я понимаю код в вопросе, он не пытается найти все простые множители ‹ 10^12, а все простые числа (поэтому он действительно ищет 11-значные простые числа). - person Aaron Digulla; 06.10.2014
comment
@AaronDigulla: Да. Если вы реализуете эффективное сито, вы начнете отмечать с p^2, так как 1, 2,..., (p-1) в p*1, p*2,..., p*(p-1 ) были рассмотрены ранее при просеивании простых чисел, меньших p. Итак, мы выполним здесь 2 шага: 1) Найдите все простые числа для sqrt(max) 2) Выполните просеивание (после просеивания просто выполните цикл по набору битов, чтобы получить простые числа). 2-й шаг объясняется в 5-м пункте. - person nhahtdh; 06.10.2014
comment
Так что это даже не объясняет, почему код дает сбой, но принимается как ответ? Как странно. - person Jim Balter; 06.10.2014
comment
@JimBalter: я отвечаю на вопрос о том, как найти простые числа меньше 1 триллиона, что является конечной целью ОП. - person nhahtdh; 07.10.2014

long *numPrimes;
...
primes = primesToSS(ceiling+1, numPrimes);
printf("\n\nThere are %d primes below %d.\n\n",*numPrimes,ceiling);

Как заметил Blastfurnace, вы разыменовываете (и сохраняете через) неинициализированный указатель. Это должно быть

long numPrimes;
...
primes = primesToSS(ceiling+1, &numPrimes);
printf("\n\nThere are %d primes below %d.\n\n", numPrimes, ceiling);

Вполне могут быть и другие ошибки... особенно не проверка сбоя malloc. О, вот один:

long *sieve;
initArray(sieve, n, 1);

...

void initArray( int* arr, int len, int n ) {
    for( int i = 0; i < len; ++i) arr[i] = n; }

int и long — разные типы. И вот вы делаете это снова:

long *primes = malloc(numPrimes*sizeof(int));

Лучшей практикой является использование sizeof элемента, а не типа, что позволяет избежать подобных вещей:

long *primes = malloc(numPrimes * sizeof *primes);

Проверьте свой код на наличие других очевидных ошибок.

Примечание по стилю: размещение } в конце строки является плохой практикой и требует ошибки, когда вы добавляете другую строку позже.

person Jim Balter    schedule 06.10.2014
comment
Вау, социопатический минус за рулем ... Я получил два за одну минуту. - person Jim Balter; 15.10.2014

Некоторые соображения по поводу памяти: для первого триллиона простых чисел вам потребуется 1 триллион/8 байт, если вы представляете каждое число одним битом. Это 125000000000 = 119209 МБ = 116 ГБ оперативной памяти.

Хотя вы можете попросить свою ОС создать раздел SWAP размером 200 ГБ, производительность, вероятно, будет плохой. Если вы хотите сохранить тот же алгоритм, вам нужно посмотреть на файлы с отображением памяти.

Это по-прежнему будет очень медленным, но, по крайней мере, вы сможете выделить достаточно большой массив.

Следующим шагом будет рассмотрение различных простых алгоритмов, или вам необходимо изучить алгоритмы сжатия памяти. В вашем случае разреженные матрицы не будут работать, так как ваша начальная матрица равна 1, и вы в конечном итоге удалить почти все элементы.

person Aaron Digulla    schedule 06.10.2014
comment
Программа ОП дает сбой, что не решается. Хотя это ответ на вопрос в первом предложении, похоже, что это не основное внимание ОП. P.S. Сито Аткина — лучший алгоритм для решения проблем с памятью. - person Jim Balter; 06.10.2014
comment
@JimBalter: Другие ответы уже объяснили ошибки. Я попытался ответить на более общие вопросы, о которых должен знать ОП. - person Aaron Digulla; 06.10.2014