Sieve Of Atkin на удивление медленный

Недавно я очень заинтересовался простыми числами и попытался написать программы для их вычисления. Я смог сделать сито из программы Sundaram, которая вычисляла миллион простых чисел за пару секунд. Я считаю, что это довольно быстро, но я хотел лучше. Затем я попытался создать решето Аткина. Я собрал рабочий код C++ за 20 минут. после копирования псевдокода из Википедии.

Я знал, что он не будет идеальным, потому что в конце концов это псевдокод. Я ожидал, по крайней мере, лучших времен, чем мое сито Sundaram, но я был так неправ. Это очень очень медленно. Я просматривал его много раз, но не нашел существенных изменений, которые можно было бы внести. Глядя на мой код, помните, я знаю, что он неэффективен, я знаю, что использовал системные команды, я знаю, что это повсюду, но это не проект или что-то важное, это для меня.

#include <iostream>
#include <fstream>
#include <time.h>
#include <Windows.h>
#include <vector>

using namespace std;

int main(){

float limit;
float slimit;
long int n;
int counter = 0;
int squarenum;
int starttime;
int endtime;
vector <bool> primes;

ofstream save;
save.open("primes.txt");
save.clear();

cout << "Find all primes up to: " << endl;
cin >> limit;

slimit = sqrt(limit);

primes.resize(limit);

starttime = time(0);

// sets all values to false
for (int i = 0; i < limit; i++){

    primes[i] = false;
}


//puts in possible primes
for (int x = 1; x <= slimit; x++){

    for (int y = 1; y <= slimit; y++){


        n = (4*x*x) + (y*y);
        if (n <= limit && (n%12 == 1 || n%12 == 5)){

            primes[n] = !primes[n];
        }

        n = (3*x*x) + (y*y);
        if (n <= limit && n% 12 == 7){

            primes[n] = !primes[n];
        }

        n = (3*x*x) - (y*y);
        if ( x > y && n <= limit && n%12 == 11){

            primes[n] = !primes[n];
        }
    }
}

//square number mark all multiples not prime

for (float i = 5; i < slimit; i++){

    if (primes[i] == true){

        for (long int k = i*i; k < limit; k = k + (i*i)){

            primes[k] = false;
        }
    }
}

endtime = time(0);
cout << endl << "Calculations complete, saving in text document" << endl;


// loads to document
for (int i = 0 ; i < limit ; i++){

    if (primes[i] == true){


        save << counter << ") " << i << endl;
        counter++;
    }
}

save << "Found in " << endtime - starttime << " seconds" << endl;

save.close();

system("primes.txt");

system ("Pause");
return 0;
}

person user2829334    schedule 04.01.2014    source источник
comment
также варианты компиляции.   -  person Mikhail    schedule 04.01.2014
comment
См. википедию. Этот псевдокод написан для ясности. Повторяющиеся и расточительные расчеты означают, что оно будет работать медленнее, чем решето Эратосфена. Чтобы повысить его эффективность, необходимо использовать более быстрые методы для нахождения решений трех квадратичных уравнений. Эффективное сито Аткина можно найти здесь: cr.yp.to/primegen.html   -  person James King    schedule 04.01.2014
comment
Загрузка чего-то, чтобы сделать это для меня, лишает цели, я понимаю, что могу просто найти это в Интернете, но я не хочу этого.   -  person user2829334    schedule 04.01.2014
comment
Есть ли причина, по которой вы использовали float вместо limit и slimit?   -  person Retired Ninja    schedule 04.01.2014
comment
Насколько я знаю, нельзя возводить целое число в квадрат, но опять же, я не очень много знаю @RetiredNinja   -  person user2829334    schedule 04.01.2014
comment
@user2829334 user2829334 Я предлагал вам прочитать исходный код Primegen, чтобы понять, как это реализовать эффективно. Это не легко. См. также предыдущие сообщения stackoverflow по этому поводу.   -  person James King    schedule 04.01.2014
comment
Обязательный комментарий по использованию профилировщика, чтобы увидеть, где находятся узкие места.   -  person EvilTeach    schedule 04.01.2014
comment
Сравнение int и float медленнооооо. Мало того, что сравнение идет медленно, так еще и многие оптимизации вылетают из окна. Использование float вместо double также означает, что ваш алгоритм будет работать неправильно где-то около 16 миллионов.   -  person gnasher729    schedule 29.07.2014


Ответы (2)


Это не совсем ответ (IMO, вы уже получили ответ в комментариях), а быстрый стандарт для сравнения. Сито Эратосфена должно найти миллион простых чисел в хорошо менее чем за секунду на достаточно современной машине.

#include <vector>
#include <iostream>
#include <time.h>

unsigned long primes = 0;

int main() {
    // empirically derived limit to get 1,000,000 primes
    int number = 15485865; 

    clock_t start = clock();
    std::vector<bool> sieve(number,false);
    sieve[0] = sieve[1] = true;

    for(int i = 2; i<number; i++) {
        if(!sieve[i]) {
            ++primes;
            for (int temp = 2*i; temp<number; temp += i)
                sieve[temp] = true;
        }
    }
    clock_t stop = clock();

    std::cout.imbue(std::locale(""));
    std::cout << "Total primes: " << primes << "\n";
    std::cout << "Time: " << double(stop - start) / CLOCKS_PER_SEC << " seconds\n";
    return 0;
}

Запустив это на моем ноутбуке, я получаю результат:

Total primes: 1000000
Time: 0.106 seconds

Очевидно, что скорость будет несколько варьироваться в зависимости от процессора, тактовой частоты и т. д., но с чем-либо достаточно современным я все равно ожидаю, что время будет меньше секунды. Конечно, если вы решите записывать простые числа в файл, вы можете ожидать, что это добавит некоторое время, но даже с этим я ожидаю, что общее время составит менее секунды — с относительно медленным жестким диском моего ноутбука, записывающим числа получают только общее количество примерно до 0,6 секунды.

person Jerry Coffin    schedule 04.01.2014
comment
Да, моя реализация soe с использованием битового сита вычисляет 1270607 менее 20 миллионов за 0,052 секунды. - person James King; 04.01.2014
comment
@ user3114046: Это, безусловно, звучит намного более респектабельно, чем пара секунд. - person Jerry Coffin; 04.01.2014
comment
Около года назад я потратил на это довольно много времени, пытаясь вычислить большие значения pi(x) с помощью моей собственной версии решета Мейселя-Лемера. Я дошел только до чего-то вроде пи (10 ^ 20). Битовое сито использует аппаратную функцию popcnt для быстрого подсчета простых чисел, что на самом деле требует больших затрат при просеивании. Я не смог найти свою ванильную реализацию SOE на C, но запуск ее на python позволяет получить 1 миллион простых чисел за 0,71 секунды. - person James King; 04.01.2014
comment
О, вы немного ускоритесь, если начнете просеивать с p^2, а не с 2*p for (int temp = i*i; temp<number; temp += i) - person James King; 04.01.2014
comment
Когда я бегу, это занимает намного больше времени. Мой ноутбук современный, поэтому я не знаю, в чем проблема. @JerryCoffin - person user2829334; 04.01.2014
comment
@user3114046: Да, я понимаю, что доступно множество оптимизаций — я просто собрал это вместе как краткий базовый уровень минимума, который вы должны ожидать даже от самой простой, наименее оптимизированной реализации. (если не прикладывать реальных усилий для преднамеренного замедления). - person Jerry Coffin; 04.01.2014
comment
Конечно. user2829334 обратите внимание, что вам нужно просеивать только до sqrt (число), это самая простая оптимизация. Обратите внимание, что если вы сделаете оптимизацию i^2 без этого, программа выдаст ошибку. - person James King; 04.01.2014
comment
@user2829334: user2829334: Возможно, вы скомпилировали без оптимизации. Если вы компилируете в командной строке, попробуйте добавить -O2 и посмотрите, не станет ли это лучше. В среде IDE вы обычно переключаетесь на выпускную сборку. - person Jerry Coffin; 04.01.2014
comment
@user3114046 Мое базовое несегментированное сито Эратосфена C++, основанное только на шансах, занимает 0,09 секунды для простых чисел до 20 млн, на медленный Ideone (который, как мне кажется, обычно в 3 раза медленнее, чем обычная современная коробка). Он считает простые числа по мере их просеивания. -- Он составляет 1 миллион простых чисел за 0,06 с. - person Will Ness; 04.01.2014
comment
Я протестировал ваш код на Ideone. Там он работает за 0,24 секунды по сравнению с вашими 0,106, что в 2,26 раза медленнее. Этот код действительно без какой-либо оптимизации, и в этом его ценность здесь, как вы и сказали, как точка наблюдения. :) (+1). - person Will Ness; 04.01.2014

вектор представляет собой набор битов. Обновление значений набора битов, которые не находятся в кеше, обходится дорого. Попробуйте вектор, так намного дешевле писать.

person Aleksandr Pakhomov    schedule 04.01.2014