Портирование оптимизированного сита Эратосфена с Python на C ++ - PullRequest
2 голосов
/ 14 марта 2011

Некоторое время назад я использовал (молниеносно) простейшее в python, которое нашел здесь: Самый быстрый способ перечислить все простые числа ниже N

Чтобы быть точным, эта реализация:

def primes2(n):
    """ Input n>=6, Returns a list of primes, 2 <= p < n """
    n, correction = n-n%6+6, 2-(n%6>1)
    sieve = [True] * (n/3)
    for i in xrange(1,int(n**0.5)/3+1):
      if sieve[i]:
        k=3*i+1|1
        sieve[      k*k/3      ::2*k] = [False] * ((n/6-k*k/6-1)/k+1)
        sieve[k*(k-2*(i&1)+4)/3::2*k] = [False] * ((n/6-k*(k-2*(i&1)+4)/6-1)/k+1)
    return [2,3] + [3*i+1|1 for i in xrange(1,n/3-correction) if sieve[i]]

Теперь я могу немного понять идею оптимизации, автоматически пропуская кратные 2, 3 и т. Д., Но когда дело доходит до переноса этого алгоритма на C ++, я застреваю (у меня хорошее понимание Python и разумный / плохое понимание C ++, но достаточно хорошее для рок-н-ролла).

То, что я сейчас прокрутил сам, это (isqrt() - это просто целочисленная функция квадратного корня):

template <class T>
void primesbelow(T N, std::vector<T> &primes) {
    T sievemax = (N-3 + (1-(N % 2))) / 2;
    T i;
    T sievemaxroot = isqrt(sievemax) + 1;

    boost::dynamic_bitset<> sieve(sievemax);
    sieve.set();

    primes.push_back(2);

    for (i = 0; i <= sievemaxroot; i++) {
        if (sieve[i]) {
            primes.push_back(2*i+3);
            for (T j = 3*i+3; j <= sievemax; j += 2*i+3) sieve[j] = 0; // filter multiples
        }
    }

    for (; i <= sievemax; i++) {
        if (sieve[i]) primes.push_back(2*i+3);
    }
}

Эта реализация достойна и автоматически пропускает кратные 2, но если бы я мог портировать реализацию Python, я думаю, что она могла бы быть намного быстрее (50% -30% или около того).

Для сравнения результатов (в надежде, что этот вопрос будет успешно дан), текущее время выполнения с N=100000000, g++ -O3 на Q6600 Ubuntu 10.10 составляет 1230 мс.

Теперь я хотел бы получить некоторую помощь в понимании того, что делает приведенная выше реализация Python, или в том, что вы перенесли бы его для меня (хотя и не так полезно).

EDIT

Некоторая дополнительная информация о том, что мне трудно.

У меня проблемы с такими методами, как корректирующая переменная и вообще как она получается. Ссылка на сайт, объясняющий различные оптимизации Eratosthenes (кроме простых сайтов, которые говорят: «ну, вы просто пропустите кратные 2, 3 и 5», а затем получите файл с 1000 строчками C), было бы здорово.

Не думаю, что у меня возникли бы проблемы со 100% прямым и буквальным портом, но, в конце концов, это для обучения, которое было бы совершенно бесполезным.

EDIT

Посмотрев на код в оригинальной numpy версии, он на самом деле довольно прост для реализации и с некоторыми соображениями, не слишком сложными для понимания. Это версия C ++, которую я придумал. Я публикую его здесь в полной версии, чтобы помочь другим читателям в случае, если им понадобится довольно эффективное простое изображение, не содержащее двух миллионов строк кода. Это простое число делает все простые числа меньше 100000000 примерно за 415 мс на той же машине, что и выше. Это в 3 раза быстрее, чем я ожидал!

#include <vector>
#include <boost/dynamic_bitset.hpp>

// http://vault.embedded.com/98/9802fe2.htm - integer square root
unsigned short isqrt(unsigned long a) {
    unsigned long rem = 0;
    unsigned long root = 0;

    for (short i = 0; i < 16; i++) {
        root <<= 1;
        rem = ((rem << 2) + (a >> 30));
        a <<= 2;
        root++;

        if (root <= rem) {
            rem -= root;
            root++;
        } else root--;

    }

    return static_cast<unsigned short> (root >> 1);
}

// /2029352/samyi-bystryi-sposob-perechislit-vse-prostye-chisla-nizhe-n#2029364
// https://stackoverflow.com/questions/5293238/porting-optimized-sieve-of-eratosthenes-from-python-to-c/5293492
template <class T>
void primesbelow(T N, std::vector<T> &primes) {
    T i, j, k, l, sievemax, sievemaxroot;

    sievemax = N/3;
    if ((N % 6) == 2) sievemax++;

    sievemaxroot = isqrt(N)/3;

    boost::dynamic_bitset<> sieve(sievemax);
    sieve.set();

    primes.push_back(2);
    primes.push_back(3);

    for (i = 1; i <= sievemaxroot; i++) {
        if (sieve[i]) {
            k = (3*i + 1) | 1;
            l = (4*k-2*k*(i&1)) / 3;

            for (j = k*k/3; j < sievemax; j += 2*k) {
                sieve[j] = 0;
                sieve[j+l] = 0;
            }

            primes.push_back(k);
        }
    }

    for (i = sievemaxroot + 1; i < sievemax; i++) {
        if (sieve[i]) primes.push_back((3*i+1)|1);
    }
}

Ответы [ 3 ]

3 голосов
/ 14 марта 2011

Я постараюсь объяснить как можно больше. Массив sieve имеет необычную схему индексации; он сохраняет бит для каждого числа, которое соответствует 1 или 5 модулю 6. Таким образом, число 6*k + 1 будет сохранено в позиции 2*k, а k*6 + 5 будет сохранено в позиции 2*k + 1. Операция 3*i+1|1 противоположна этой: она принимает числа вида 2*n и преобразует их в 6*n + 1, принимает 2*n + 1 и преобразует в 6*n + 5 (вещь +1|1 преобразует 0 до 1 и 3 до 5). Основной цикл повторяет k по всем числам с этим свойством, начиная с 5 (когда i равно 1); i - соответствующий индекс в sieve для числа k. Обновление первого среза до sieve затем очищает все биты в сите с индексами вида k*k/3 + 2*m*k (для m натуральное число); соответствующие цифры для этих индексов начинаются с k^2 и увеличиваются на 6*k на каждом шаге. Обновление второго среза начинается с индекса k*(k-2*(i&1)+4)/3 (число k * (k+4) для k, совпадающего с 1 mod 6 и k * (k+2) в противном случае) и аналогичным образом увеличивает число на 6*k на каждом шаге.

Вот еще одна попытка объяснения: пусть candidates будет набором всех чисел, которые по крайней мере 5 и совпадают либо с 1, либо с 5 mod 6. Если вы умножите два элемента в этом наборе, вы получите другой элемент в наборе. Пусть succ(k) для некоторого k в candidates будет следующим элементом (в числовом порядке) в candidates, который больше k. В этом случае внутренняя петля сита в основном (с использованием обычной индексации для sieve):

for k in candidates:
  for (l = k; ; l += 6) sieve[k * l] = False
  for (l = succ(k); ; l += 6) sieve[k * l] = False

Из-за ограничений на то, какие элементы хранятся в sieve, это то же самое, что:

for k in candidates:
  for l in candidates where l >= k:
    sieve[k * l] = False

, который удалит все кратные k в candidates (кроме самого k) из сита в некоторый момент (либо когда текущий k использовался как l ранее, либо когда он используется как k сейчас).

1 голос
/ 22 марта 2011

В ответ на ответ Говарда Хиннанта, Говард, вам не нужно проверять числа в наборе всех натуральных чисел, которые не делятся на 2, 3 или 5 для простоты, как таковой.Вам нужно просто умножить каждое число в массиве (за исключением 1, который самоуничтожается) на свое время и на каждое последующее число в массиве.Эти перекрывающиеся продукты дадут вам все непростые числа в массиве вплоть до того момента, когда вы расширяете детерминистско-мультипликативный процесс.Таким образом, первое не простое число в массиве будет 7 в квадрате или 49. Второе, 7, умноженное на 11 или 77 и т. Д. Полное объяснение здесь: http://www.primesdemystified.com

0 голосов
/ 14 марта 2011

Помимо этого, вы можете «приблизить» простые числа.Назовите приблизительное простое число P. Вот несколько формул:

P = 2 * k + 1 // не делится на 2

P = 6 * k + {1, 5} //не делится на 2, 3

P = 30 * k + {1, 7, 11, 13, 17, 19, 23, 29} // не делится на 2, 3, 5

Свойства набора чисел, найденные этими формулами, состоят в том, что P может не быть простым, однако все простые числа находятся в наборе PIe, если вы проверяете числа в множестве P на простое число, вы не пропустите ни одного.

Вы можете переформулировать эти формулы следующим образом:

P = X * k + {-i, -j, -k, k, j, i}

, если это более удобно для вас.

Здесь - это некоторый код, который использует эту технику с формулой для P, не делимой на 2, 3, 5, 7.

Эта ссылка может представлять экстент докоторый эта техника может быть практически использована.

...