Поиск простых чисел с многопоточностью

пытаюсь написать максимально быструю программу для поиска простых чисел. Пока лучший результат (он представлен в коде) находит все простые чиса до миллиона за 0,971 секунду на моем компьютере. Алгоритмически более никак результат не могу улучшить (возможно, вы знаете как, я с радостью послушаю), пришло в голову прикрутить многопоток. В питоне это можно было сделать относительно просто с помощью дополнительной библиотеки Numba. А как с этим состоят дела в C++? Просто сколько искал, не смог найти внятного объяснения. И имеет ли вообще смысл улучшать дальше или даже идеально работающий многопоток не даст никакого прироста (тогда почему?)?

P.S. Функцию вызываю три раза просто для большей точности, в качестве значений беру среднее по 3м измерениям

 #include <iostream>
 using namespace std;

void IsPrime() {

float start_time = clock(); //Засекаем время начала

int n = 10000000; //Задаем число, до которого будем перебирать.
int count = 0; //Переменная count - количество простых чисел, которорое мы получим после работы алгоритма.

for (int i = 3; i < n + 1; i+=2) { //Алгоритм перебора
    bool check = true;
    for (int j = 3; j < (int)sqrt(i)+1; j+=2) {
        if (i % j == 0) {
            check = false;
            break;
        }
    }
    if (check) {
        count++;
    }
    else {
        check = true;
    }
}

float end_time = clock(); //Засекаем время  конца
float final_time = (end_time - start_time) / 1000.0; //Считаем итоговое время
cout << "Всего простых чисел: " << count+1 << '\n' << "Времени потрачено: " << final_time << " секунд" << endl;
}

int main()
{
setlocale(LC_ALL, "RUS");

IsPrime(); //Первый замер
IsPrime(); //Второй замер
IsPrime(); //Третий замер

}

Ответы (2 шт):

Автор решения: Zhihar

попробуйте воспользоватьcя тем свойством простых чисел, что любое простое число больше 3 представимо в виде 6k - 1 или 6k + 1

if (n > 10)
    count = 4;

for (int i = 5; i < n + 1; i += 6) {

    const int limit = (int)sqrt(i) + 6;

    count++;

    for (int j = 6; j < limit; j += 6) {
        if ((i % (j - 1) == 0) || (i % (j + 1) == 0)) {
            count--;
            break;
        }
    }
}

for (int i = 7; i < n + 1; i += 6) {

    const int limit = (int)sqrt(i) + 6;

    count++;

    for (int j = 6; j < limit; j += 6) {
        if ((i % (j - 1) == 0) || (i % (j + 1) == 0)) {
            count--;
            break;
        }
    }
}
→ Ссылка
Автор решения: Stanislav Volodarskiy

Битовое решето Эратосфена для нечётных чисел. Это последовательный алгоритм.

Когда построены простые до миллиарда, можно применить сегментированное решето Эратосфена: числа от 10^9 до 10^18 разбить на сегменты по миллиарду. В каждом сегменте выполнять просеивание. Это уже параллельный алгоритм. Когда очередное ядро просеяло очередной сегмент его печатают и выбрасывают.

Битовая маска одного сегмента 64MB, скажем, восемь сегментов параллельно - полгига. 203 мегабайта нужно для простых до миллиарда. На всё про всё не больше гига и вы будете просеивать простые со скоростью миллиард чисел в секунду (загибаю, такая производительность будет только для первых сегментов, дальше будет медленнее). Процесс закончится через миллиард секунд (32 года) - вы дойдёте до 10^18.

Процесс можно распределить между любым количеством компьютеров. Обработка отдельных сегментов не зависит друг от друга.

Но если вы действительно решите идти этим путём, то есть ещё более эффективные алгоритмы.

#include <iostream>
#include <vector>

void print_primes(const unsigned long long n) {
    const unsigned long long n2 = n / 2;
    std::vector<bool> sieve(n2, true);
    if (2 < n) {
        std::cout << 2 << '\n';
        unsigned long long k;
        for (k = 1; ; ++k) {
            if (sieve[k]) {
                const unsigned long long i = 2 * k + 1;
                const unsigned long long i2 = i * i;
                if (i2 >= n) {
                    break;
                }
                std::cout << i << '\n';
                for (unsigned long long j = i2 / 2; j < n2; j += i) {
                    sieve[j] = false;
                }
            }
        }
        for (; k < n2; ++k) {
            if (sieve[k]) {
                std::cout << 2 * k + 1 << '\n';
            }
        }
    }
}

int main() {
    unsigned long long n;
    if (std::cin >> n) {
        print_primes(n);
    }
}

Девять секунд до миллиарда:

$ g++ -std=c++17 -pedantic -Wall -Wextra -Werror -O3 sieve-of-eratosthenes.cpp

$ time echo 10000000 | ./a.out | wc -l
664579

real  0m0.073s
user  0m0.068s
sys   0m0.012s

$ time echo 100000000 | ./a.out | wc -l
5761455

real  0m0.707s
user  0m0.756s
sys   0m0.028s

$ time echo 1000000000 | ./a.out | wc -l
50847534

real  0m8.295s
user  0m8.808s
sys   0m0.304s
→ Ссылка