Самый быстрый способ вычисления простых чисел в С#?

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

int Until = 20000000;
BitArray PrimeBits = new BitArray(Until, true);

/*
 * Sieve of Eratosthenes
 * PrimeBits is a simple BitArray where all bit is an integer
 * and we mark composite numbers as false
 */

PrimeBits.Set(0, false); // You don't actually need this, just
PrimeBits.Set(1, false); // remindig you that 2 is the smallest prime

for (int P = 2; P < (int)Math.Sqrt(Until) + 1; P++)
    if (PrimeBits.Get(P))
        // These are going to be the multiples of P if it is a prime
        for (int PMultiply = P * 2; PMultiply < Until; PMultiply += P)
            PrimeBits.Set(PMultiply, false);

// We use this to store the actual prime numbers
List<int> Primes = new List<int>();

for (int i = 2; i < Until; i++)
    if (PrimeBits.Get(i))
        Primes.Add(i);

Возможно, я мог бы использовать несколько BitArray и BitArray.And() их вместе?

Ответ 1

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

Кроме того, при удалении более поздних композитов, когда вы впервые нажмете новый простой p - первый составной кратный оставшегося p будет p * p, так как все до этого уже было устранено. Фактически, вам нужно только умножить p на все оставшиеся потенциальные простые числа, оставшиеся после него в списке, останавливаясь, как только ваш продукт выходит за пределы диапазона (больше, чем до).

Есть также некоторые хорошие вероятностные алгоритмы, такие как тест Миллера-Рабина. Страница wikipedia - хорошее введение.

Ответ 2

Параллелизация в сторону, вы не хотите вычислять sqrt (до) на каждой итерации. Вы также можете считать кратные 2, 3 и 5 и рассчитывать только для N% 6 в {1,5} или N% 30 в {1,7,11,13,17,19,23,29}.

Вы можете легко распараллелить алгоритм факторинга, так как N-й этап зависит только от результата sqrt (n), поэтому через некоторое время конфликтов не будет. Но это не хороший алгоритм, так как он требует большого разделения.

Вы также должны иметь возможность распараллеливать решетчатые алгоритмы, если у вас есть рабочие документы-записи, которые гарантированно будут заполнены перед чтением. В основном, писатели не должны конфликтовать с читателем - по крайней мере, как только вы сделали несколько записей, они должны работать по крайней мере на N выше читателя, так что вам потребуется только синхронизированное чтение достаточно редко (когда N превышает последнее синхронизированное чтение стоимость). Вам не нужно синхронизировать массив bool через любое количество потоков сообщений, поскольку конфликты записи не возникают (в худшем случае более одного потока будет писать true в одно и то же место).

Основной проблемой было бы обеспечить, чтобы любой рабочий, которого ждали, чтобы написать, завершил. В С++ вы должны использовать команду compare-and-set для переключения на рабочего, которого ждет в любой момент. Я не С# wonk, поэтому не знаю, как это сделать на этом языке, но функция Win32 InterlockedCompareExchange должна быть доступна.

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

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

Ответ 3

Без профилирования мы не можем определить, какой бит программы нуждается в оптимизации.

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

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

Ответ 4

@DrPizza Профилирование действительно помогает улучшить реализацию, не раскрывает возможности для параллельного выполнения или предлагает лучшие алгоритмы (если у вас нет опыта в противном случае, и в этом случае мне бы очень хотелось увидеть ваш профилировщик).

У меня есть только одноядерные машины дома, но я использовал эквивалент Java вашего сита BitArray и однопоточную версию инверсии сита - удерживая маркеры в массиве и используя wheel, чтобы сократить пространство поиска в пять раз, а затем маркировать бит массива с шагом в колесе, используя каждую маркировку prime. Он также уменьшает хранение до O (sqrt (N)) вместо O (N), что помогает как с точки зрения наибольшего N, пейджинга, так и полосы пропускания.

Для средних значений N (от 1e8 до 1e12) простые числа до sqrt (N) могут быть найдены довольно быстро, и после этого вы сможете легко сравнивать параллельный поиск на процессоре. На моей одноядерной машине подход с колесами обнаруживает штрихи до 1e9 в 28 секунд, тогда как ваше сито (после перемещения sqrt из петли) занимает 86 секунд - улучшение связано с колесом; инверсия означает, что вы можете обрабатывать N больше 2 ^ 32, но делает его медленнее. Код можно найти здесь. Вы могли бы параллелизовать вывод результатов из наивного сита после того, как вы пройдете мимо sqrt (N), так как после этого бит бит-массив не изменяется; но как только вы имеете дело с N достаточно большим, чтобы он имел значение, размер массива слишком велик для ints.

Ответ 5

Вы также должны рассмотреть возможное изменение алгоритмов.

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

Возможно, предубеждение пространства для вашего списка, сделает его более дешевым для создания/заполнения.

Ответ 6

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

Вы также можете взглянуть на Microsoft Parallel FX Library, чтобы сделать ваш существующий код многопоточным, чтобы использовать многоядерные системы. С минимальными изменениями кода вы можете сделать для циклов многопоточными.

Ответ 7

Там очень хорошая статья о сите Эратосфена: Подлинное сито Эратосфена

Это в функциональной настройке, но большая часть opimization также применяется к процедурной реализации на С#.

Две наиболее важные оптимизации - это начать выходить на P ^ 2 вместо 2 * P и использовать колесо для следующих простых чисел.

Для concurrency вы можете обрабатывать все числа до P ^ 2 параллельно P без какой-либо ненужной работы.

Ответ 8

    void PrimeNumber(long number)
    {
        bool IsprimeNumber = true;
        long  value = Convert.ToInt32(Math.Sqrt(number));
        if (number % 2 == 0)
        {
            IsprimeNumber = false;
            MessageBox.Show("No It is not a Prime NUmber");
            return;
        }
        for (long i = 3; i <= value; i=i+2)
        {             
           if (number % i == 0)
            {

                MessageBox.Show("It is divisible by" + i);
                IsprimeNumber = false;
                break;
            }

        }
        if (IsprimeNumber)
        {
            MessageBox.Show("Yes Prime NUmber");
        }
        else
        {
            MessageBox.Show("No It is not a Prime NUmber");
        }
    }