C #: реализация решета Аткина

Мне было интересно, есть ли у кого-нибудь здесь хорошая реализация Сита Аткина, которой они хотели бы поделиться.

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

public class Atkin : IEnumerable<ulong>
{
    private readonly List<ulong> primes;
    private readonly ulong limit;

    public Atkin(ulong limit)
    {
        this.limit = limit;
        primes = new List<ulong>();
    }

    private void FindPrimes()
    {
        var isPrime = new bool[limit + 1];
        var sqrt = Math.Sqrt(limit);

        for (ulong x = 1; x <= sqrt; x++)
            for (ulong y = 1; y <= sqrt; y++)
            {
                var n = 4*x*x + y*y;
                if (n <= limit && (n % 12 == 1 || n % 12 == 5))
                    isPrime[n] ^= true;

                n = 3*x*x + y*y;
                if (n <= limit && n % 12 == 7)
                    isPrime[n] ^= true;

                n = 3*x*x - y*y;
                if (x > y && n <= limit && n % 12 == 11)
                    isPrime[n] ^= true;
            }

        for (ulong n = 5; n <= sqrt; n++)
            if (isPrime[n])
                for (ulong k = n*n; k <= limit; k *= k)
                    isPrime[k] = false;

        primes.Add(2);
        primes.Add(3);
        for (ulong n = 5; n <= limit; n++)
            if (isPrime[n])
                primes.Add(n);
    }


    public IEnumerator<ulong> GetEnumerator()
    {
        if (!primes.Any())
            FindPrimes();


        foreach (var p in primes)
            yield return p;
    }


    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

Я практически только что попытался «перевести» псевдокод, указанный в Википедии, но это не не работает правильно. Так что либо я что-то неправильно понял, либо просто сделал что-то не так. Или, скорее всего, оба ...

У меня есть список из первых 500 простых чисел, которые я использую в качестве теста, и моя реализация терпит неудачу под номером 40 (или 41?).

Значения различаются в индексе [40]
Ожидается: 179
Но было: 175

Сможете ли вы найти мою ошибку, есть ли у вас реализация, которой вы могли бы поделиться, или и то, и другое?


Точный тест, который я использую, выглядит так:

public abstract class AtkinTests
{
    [Test]
    public void GetEnumerator_FirstFiveHundredNumbers_AreCorrect()
    {
        var sequence = new Atkin(2000000);
        var actual = sequence.Take(500).ToArray();
        var expected = First500;

        CollectionAssert.AreEqual(expected, actual);
    }

    private static readonly ulong[] First500 = new ulong[]
        {
            2, 3, 5, 7, 11, 13, 17, ...
        };
}

person Svish    schedule 14.10.2009    source источник
comment
Я сделал реализацию, которая немного быстрее вашей и намного быстрее на многоядерном компьютере, см. alicebobandmallory.com/articles/2010/01/14/   -  person Jonas Elfström    schedule 15.01.2010


Ответы (6)


Этот код:

for (ulong k = n*n; k <= limit; k *= k)
  isPrime[k] = false;

не кажется точным переводом этого псевдокода:

is_prime(k) ← false, k ∈ {n², 2n², 3n², ..., limit}

Ваш код выглядит так, как будто он будет работать для n * n, n ^ 4, n ^ 8 и т.д., то есть каждый раз возводя в квадрат вместо того, чтобы каждый раз добавлять n-квадрат. Попробуй это:

ulong nSquared = n * n;
for (ulong k = nSquared; k <= limit; k += nSquared)
  isPrime[k] = false;
person itowlson    schedule 14.10.2009
comment
Потрясающий! Похоже, что так оно и было на самом деле ... и я был немного не уверен насчет этой точной части, просто не знал, как ее правильно написать: P - person Svish; 15.10.2009

последний ответ Аарона Мугатройда из переведенного источника Python для Sieve of Atkin (SoA) не так уж и плох, но его можно улучшить в нескольких отношениях, поскольку в нем отсутствуют некоторые важные оптимизации, а именно:

  1. В его ответе используется не полная исходная версия сита Аткина и Бернштейна по модулю 60, а скорее немного улучшенная вариация псевдокода из статьи в Википедии, поэтому используется коэффициент 0,36 от числового диапазона сита, объединенного операций переключения / отбраковки; в моем приведенном ниже коде используется достаточно эффективный псевдокод внестраничного сегмента в соответствии с моими комментариями в ответе, комментирующем решето Аткина который использует коэффициент примерно в 0,26 раза больше числового диапазона, чтобы уменьшить объем выполняемой работы примерно до двух седьмых раз.

  2. Его код уменьшает размер буфера, имея только представления нечетных чисел, тогда как мой код содержит дополнительные битовые пакеты, чтобы исключить любое представление чисел, делимых на три и пять, а также тех, которые делятся на два, подразумеваемых "только нечетными"; это снижает потребность в памяти почти вдвое (до 8/15) и помогает лучше использовать кеши ЦП для дальнейшего увеличения скорости за счет уменьшения среднего времени доступа к памяти.

  3. Мой код подсчитывает количество простых чисел, используя технику подсчета всплывающих окон быстрой таблицы поиска (LUT), чтобы на подсчет почти не уходило времени по сравнению с примерно одной секундой, используя технику побитовой обработки, которую он использует; однако в этом примере кода даже это небольшое время отведено из временной части кода.

  4. Наконец, мой код оптимизирует операции манипулирования битами для минимума кода на внутренний цикл. Например, он не использует непрерывный сдвиг вправо на единицу для получения индекса нечетного представления, а фактически совсем немного сдвигает бит, записывая все внутренние циклы как операции с постоянным модулем (равным битовой позиции). Кроме того, переведенный код Аарона довольно неэффективен в операциях, так как, например, при отбраковке без простых квадратов он добавляет квадрат простого числа к индексу, а затем проверяет нечетный результат, а не просто добавляет два раза больше квадрата, чтобы не требовать проверки. ; затем он делает даже проверку избыточной, сдвигая число вправо на единицу (деление на два) перед выполнением операции отсечения во внутреннем цикле, как это происходит для всех циклов. Этот неэффективный код не будет иметь большого значения во времени выполнения для больших диапазонов с использованием метода «большого ситового буферного массива», поскольку большая часть времени на операцию используется для доступа к оперативной памяти (около 37 тактовых циклов ЦП или более для диапазон в один миллиард), но сделает время выполнения намного медленнее, чем нужно для меньших диапазонов, которые помещаются в кеш-память ЦП; другими словами, он устанавливает слишком высокий нижний предел скорости выполнения для каждой операции.

Код выглядит следующим образом:

//Sieve of Atkin based on full non page segmented modulo 60 implementation...

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

namespace NonPagedSoA {
  //implements the non-paged Sieve of Atkin (full modulo 60 version)...
  class SoA : IEnumerable<ulong> {
    private ushort[] buf = null;
    private long cnt = 0;
    private long opcnt = 0;
    private static byte[] modPRMS = { 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 49, 53, 59, 61 };
    private static ushort[] modLUT;
    private static byte[] cntLUT;
    //initialize the private LUT's...
    static SoA() {
      modLUT = new ushort[60];
      for (int i = 0, m = 0; i < modLUT.Length; ++i) {
        if ((i & 1) != 0 || (i + 7) % 3 == 0 || (i + 7) % 5 == 0) modLUT[i] = 0;
        else modLUT[i] = (ushort)(1 << (m++));
      }
      cntLUT = new byte[65536];
      for (int i = 0; i < cntLUT.Length; ++i) {
        var c = 0;
        for (int j = i; j > 0; j >>= 1) c += j & 1;
        cntLUT[i] = (byte)c;
      }
    }
    //initialization and all the work producing the prime bit array done in the constructor...
    public SoA(ulong range) {
      this.opcnt = 0;
      if (range < 7) {
        if (range > 1) {
          cnt = 1;
          if (range > 2) this.cnt += (long)(range - 1) / 2;
        }
        this.buf = new ushort[0];
      }
      else {
        this.cnt = 3;
        var nrng = range - 7; var lmtw = nrng / 60;
        //initialize sufficient wheels to non-prime
        this.buf = new ushort[lmtw + 1];

        //Put in candidate primes:
        //for the 4 * x ^ 2 + y ^ 2 quadratic solution toggles - all x odd y...
        ulong n = 6; // equivalent to 13 - 7 = 6...
        for (uint x = 1, y = 3; n <= nrng; n += (x << 3) + 4, ++x, y = 1) {
          var cb = n; if (x <= 1) n -= 8; //cancel the effect of skipping the first one...
          for (uint i = 0; i < 15 && cb <= range; cb += (y << 2) + 4, y += 2, ++i) {
            var cbd = cb / 60; var cm = modLUT[cb % 60];
            if (cm != 0)
              for (uint c = (uint)cbd, my = y + 15; c < buf.Length; c += my, my += 30) {
                buf[c] ^= cm; // ++this.opcnt;
              }
          }
        }
        //for the 3 * x ^ 2 + y ^ 2 quadratic solution toggles - x odd y even...
        n = 0; // equivalent to 7 - 7 = 0...
        for (uint x = 1, y = 2; n <= nrng; n += ((x + x + x) << 2) + 12, x += 2, y = 2) {
          var cb = n;
          for (var i = 0; i < 15 && cb <= range; cb += (y << 2) + 4, y += 2, ++i) {
            var cbd = cb / 60; var cm = modLUT[cb % 60];
            if (cm != 0)
              for (uint c = (uint)cbd, my = y + 15; c < buf.Length; c += my, my += 30) {
                buf[c] ^= cm; // ++this.opcnt;
              }
          }
        }
        //for the 3 * x ^ 2 - y ^ 2 quadratic solution toggles all x and opposite y = x - 1...
        n = 4; // equivalent to 11 - 7 = 4...
        for (uint x = 2, y = x - 1; n <= nrng; n += (ulong)(x << 2) + 4, y = x, ++x) {
          var cb = n; int i = 0;
          for ( ; y > 1 && i < 15 && cb <= nrng; cb += (ulong)(y << 2) - 4, y -= 2, ++i) {
            var cbd = cb / 60; var cm = modLUT[cb % 60];
            if (cm != 0) {
              uint c = (uint)cbd, my = y;
              for ( ; my >= 30 && c < buf.Length; c += my - 15, my -= 30) {
                buf[c] ^= cm; // ++this.opcnt;
              }
              if (my > 0 && c < buf.Length) { buf[c] ^= cm; /* ++this.opcnt; */ }
            }
          }
          if (y == 1 && i < 15) {
            var cbd = cb / 60; var cm = modLUT[cb % 60];
            if ((cm & 0x4822) != 0 && cbd < (ulong)buf.Length) { buf[cbd] ^= cm; /* ++this.opcnt; */ }
          }
        }

        //Eliminate squares of base primes, only for those on the wheel:
        for (uint i = 0, w = 0, pd = 0, pn = 0, msk = 1; w < this.buf.Length ; ++i) {
          uint p = pd + modPRMS[pn];
          ulong sqr = (ulong)p * (ulong)p; //to handle ranges above UInt32.MaxValue
          if (sqr > range) break;
          if ((this.buf[w] & msk) != 0) { //found base prime, square free it...
            ulong s = sqr - 7;
            for (int j = 0; s <= nrng && j < modPRMS.Length; s = sqr * modPRMS[j] - 7, ++j) {
              var cd = s / 60; var cm = (ushort)(modLUT[s % 60] ^ 0xFFFF);
              //may need ulong loop index for ranges larger than two billion
              //but buf length only good to about 2^31 * 60 = 120 million anyway,
              //even with large array setting and half that with 32-bit...
              for (ulong c = cd; c < (ulong)this.buf.Length; c += sqr) {
                this.buf[c] &= cm; // ++this.opcnt;
              }
            }
          }
          if (msk >= 0x8000) { msk = 1; pn = 0; ++w; pd += 60; }
          else { msk <<= 1; ++pn; }
        }

        //clear any overflow primes in the excess space in the last wheel/word:
        var ndx = nrng % 60; //clear any primes beyond the range
        for (; modLUT[ndx] == 0; --ndx) ;
        this.buf[lmtw] &= (ushort)((modLUT[ndx] << 1) - 1);
      }
    }

    //uses a fast pop count Look Up Table to return the total number of primes...
    public long Count {
      get {
        long cnt = this.cnt;
        for (int i = 0; i < this.buf.Length; ++i) cnt += cntLUT[this.buf[i]];
        return cnt;
      }
    }

    //returns the number of toggle/cull operations used to sieve the prime bit array...
    public long Ops {
      get {
        return this.opcnt;
      }
    }

    //generate the enumeration of primes...
    public IEnumerator<ulong> GetEnumerator() {
      yield return 2; yield return 3; yield return 5;
      ulong pd = 0;
      for (uint i = 0, w = 0, pn = 0, msk = 1; w < this.buf.Length; ++i) {
        if ((this.buf[w] & msk) != 0) //found a prime bit...
          yield return pd + modPRMS[pn]; //add it to the list
        if (msk >= 0x8000) { msk = 1; pn = 0; ++w; pd += 60; }
        else { msk <<= 1; ++pn; }
      }
    }

    //required for the above enumeration...
    IEnumerator IEnumerable.GetEnumerator() {
      return this.GetEnumerator();
    }
  }

  class Program {
    static void Main(string[] args) {
      Console.WriteLine("This program calculates primes by a simple full version of the Sieve of Atkin.\r\n");

      const ulong n = 1000000000;

      var elpsd = -DateTime.Now.Ticks;

      var gen = new SoA(n);

      elpsd += DateTime.Now.Ticks;

      Console.WriteLine("{0} primes found to {1} using {2} operations in {3} milliseconds.", gen.Count, n, gen.Ops, elpsd / 10000);

      //Output prime list for testing...
      //Console.WriteLine();
      //foreach (var p in gen) {
      //  Console.Write(p + " ");
      //}
      //Console.WriteLine();

//Test options showing what one can do with the enumeration, although more slowly...
//      Console.WriteLine("\r\nThere are {0} primes with the last one {1} and the sum {2}.",gen.Count(),gen.Last(),gen.Sum(x => (long)x));

      Console.Write("\r\nPress any key to exit:");
      Console.ReadKey(true);
      Console.WriteLine();
    }
  }
}

Этот код работает примерно в два раза быстрее, чем код Аарона (около 2,7 секунды в 64-разрядном или 32-разрядном режиме на i7-2700K (3,5 ГГц) с буфером около 16,5 мегабайт и около 0,258 миллиарда комбинированных операций отсечения без квадратов с переключением / простыми квадратами). (что можно показать, раскомментировав операторы "++ this.opcnt") для диапазона сита в один миллиард, по сравнению с 5,4 / 6,2 секунды (32-бит / 64-бит) для его кода без учета времени и почти вдвое больше памяти, используя около 0,359 миллиарда комбинированных операций переключения / отсеивания для просеивания до одного миллиарда.

Хотя это быстрее, чем его наиболее оптимизированная наивная реализация непрозрачного «Сита Эратосфена» (SoE), который не делает Сито Аткина быстрее, чем Сито Эратосфена, как будто применяет методы, аналогичные тем, которые использовались в приведенной выше реализации SoA, к SoE плюс использует максимальную факторизацию колеса, SoE будет примерно с той же скоростью, что и эта.

Анализ. Хотя количество операций для полностью оптимизированной SoE примерно такое же, как количество операций для SoA для диапазона сит в один миллиард, основным узким местом для этих невыгружаемых реализаций является память. доступ, когда размер ситового буфера превышает размеры кеш-памяти ЦП (32 килобайта кеш-памяти L1 при доступе за один тактовый цикл, 256 килобайт кеш-памяти L2 при времени доступа около четырех тактовых циклов и 8 мегабайт кеш-памяти L3 при времени доступа примерно 20 тактовых циклов для моего i7), после чего доступ к памяти может превышать сотню тактов.

Теперь у обоих есть примерно восьмикратное улучшение скорости доступа к памяти, если адаптировать алгоритмы к сегментации страниц, чтобы можно было отсеивать диапазоны, которые иначе не поместились бы в доступную память. Тем не менее, SoE продолжает выигрывать по сравнению с SoA, поскольку диапазон сита начинает становиться очень большим из-за трудностей в реализации части алгоритма «без квадратов простых чисел» из-за огромных успехов в сканировании отбраковки, которые быстро увеличиваются до многих сотен раз. размер буферов страницы. Кроме того, и, возможно, более серьезно, он требует очень много памяти и / или вычислительных ресурсов для вычисления новой начальной точки для каждого значения 'x' относительно значения 'y' в самом низком представлении каждого буфера страницы для дальнейшего довольно большая потеря эффективности выгружаемой SoA по сравнению с SoE по мере увеличения диапазона.

EDIT_ADD: SoE только для вероятностей, используемый Аароном Мургатройдом, использует около 1,026 миллиарда операций отбраковки для диапазона сита в один миллиард, что примерно в четыре раза больше операций, чем SoA, и, следовательно, должно выполняться примерно в четыре раза медленнее. , но SoA, даже реализованная здесь, имеет более сложный внутренний цикл, и особенно из-за гораздо более высокой доли отбраковок SoE только с учетом вероятности имеют гораздо более короткий шаг в сканировании отсева, чем шаги SoA. имеет гораздо лучшее среднее время доступа к памяти, несмотря на то, что размер ситового буфера значительно превышает размер кеш-памяти ЦП (лучшее использование ассоциативности кеша). Это объясняет, почему вышеупомянутая SoA всего примерно в два раза быстрее, чем SoE, рассчитанная только на разногласия, хотя теоретически может показаться, что она выполняет только четверть работы.

Если бы можно было использовать аналогичный алгоритм с использованием внутренних циклов с постоянным модулем по модулю, как для приведенной выше SoA, и реализовать ту же факторизацию колеса 2/3/5, SoE уменьшил бы количество операций отсечения примерно до 0,405 миллиарда операций, так что только примерно на 50% больше операций, чем SoA, и теоретически будет работать немного медленнее, чем SoA, но может работать примерно с той же скоростью, потому что шаги отбраковки все еще немного меньше, чем для SoA в среднем при таком "наивном" использовании большого буфера памяти. Увеличение факторизации колеса до колеса 2/3/5/7 означает, что операции отбраковки SoE снижаются примерно до 0,314 для диапазона отбраковки в один миллиард, и может заставить эту версию SoE работать примерно с той же скоростью для этого алгоритма.

Дальнейшее использование факторизации колеса может быть выполнено путем предварительного отбора массива сит (копирование в шаблон) для основных факторов 2/3/5/7/11/13/17/19 почти без затрат времени выполнения, чтобы уменьшить общее количество операций отбраковки составляет около 0,251 миллиарда для диапазона сит в один миллиард, и SoE будет работать быстрее или примерно с той же скоростью, чем даже эта оптимизированная версия SoA, даже для этих больших версий буфера памяти, при этом SoE все еще имеет много меньшая сложность кода, чем указано выше.

Таким образом, можно увидеть, что количество операций для SoE может быть значительно сокращено по сравнению с наивной или даже версией факторизации колеса только с коэффициентами 2/3/5, так что количество операций примерно такое же, как для SoA, в то время как в то же время время на операцию может быть меньше из-за менее сложных внутренних циклов и более эффективного доступа к памяти. END_EDIT_ADD

EDIT_ADD2: здесь я добавляю код для SoE, используя аналогичную технику постоянного модуля / битовой позиции для самых внутренних циклов, как для SoA выше, в соответствии с псевдокодом далее по ответу, как указано выше. Код несколько менее сложен, чем приведенный выше SoA, несмотря на то, что в нем применена высокая факторизация колеса и предварительная отбраковка, так что общее количество операций отбраковки на самом деле меньше, чем объединенные операции переключения / отбраковки для SoA вплоть до диапазона просеивания. около двух миллиардов. Код следующий:

EDIT_FINAL исправленный код ниже и комментарии к нему END_EDIT_FINAL

//Sieve of Eratosthenes based on maximum wheel factorization and pre-culling implementation...

using System;
using System.Collections;
using System.Collections.Generic;

namespace NonPagedSoE {
  //implements the non-paged Sieve of Eratosthenes (full modulo 210 version with preculling)...
  class SoE : IEnumerable<ulong> {
    private ushort[] buf = null;
    private long cnt = 0;
    private long opcnt = 0;
    private static byte[] basePRMS = { 2, 3, 5, 7, 11, 13, 17, 19 };
    private static byte[] modPRMS = { 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, //positions + 23
                                      97, 101, 103, 107, 109, 113, 121, 127, 131, 137, 139, 143, 149, 151, 157, 163,
                                      167, 169, 173, 179, 181 ,187 ,191 ,193, 197, 199, 209, 211, 221, 223, 227, 229 };
    private static byte[] gapsPRMS = { 6, 2, 6, 4, 2, 4, 6, 6, 2, 6, 4, 2, 6, 4, 6, 8,
                                       4, 2, 4, 2, 4, 8, 6, 4, 6, 2, 4, 6, 2, 6, 6, 4,
                                       2, 4, 6, 2, 6, 4, 2, 4, 2, 10, 2, 10, 2, 4, 2, 4 };
    private static ulong[] modLUT;
    private static byte[] cntLUT;
    //initialize the private LUT's...
    static SoE() {
      modLUT = new ulong[210];
      for (int i = 0, m = 0; i < modLUT.Length; ++i) {
        if ((i & 1) != 0 || (i + 23) % 3 == 0 || (i + 23) % 5 == 0 || (i + 23) % 7 == 0) modLUT[i] = 0;
        else modLUT[i] = 1UL << (m++);
      }
      cntLUT = new byte[65536];
      for (int i = 0; i < cntLUT.Length; ++i) {
        var c = 0;
        for (int j = i ^ 0xFFFF; j > 0; j >>= 1) c += j & 1; //reverse logic; 0 is prime; 1 is composite
        cntLUT[i] = (byte)c;
      }
    }
    //initialization and all the work producing the prime bit array done in the constructor...
    public SoE(ulong range) {
      this.opcnt = 0;
      if (range < 23) {
        if (range > 1) {
          for (int i = 0; i < modPRMS.Length; ++i) if (modPRMS[i] <= range) this.cnt++; else break;
        }
        this.buf = new ushort[0];
      }
      else {
        this.cnt = 8;
        var nrng = range - 23; var lmtw = nrng / 210; var lmtwt3 = lmtw * 3; 
        //initialize sufficient wheels to prime
        this.buf = new ushort[lmtwt3 + 3]; //initial state of all zero's is all potential prime.

        //initialize array to account for preculling the primes of 11, 13, 17, and 19;
        //(2, 3, 5, and 7 already eliminated by the bit packing to residues).
        for (int pn = modPRMS.Length - 4; pn < modPRMS.Length; ++pn) {
          uint p = modPRMS[pn] - 210u; ulong pt3 = p * 3;
          ulong s = p * p - 23;
          ulong xrng = Math.Min(9699709, nrng); // only do for the repeating master pattern size
          ulong nwrds = (ulong)Math.Min(138567, this.buf.Length);
          for (int j = 0; s <= xrng && j < modPRMS.Length; s += p * gapsPRMS[(pn + j++) % 48]) {
            var sm = modLUT[s % 210];
            var si = (sm < (1UL << 16)) ? 0UL : ((sm < (1UL << 32)) ? 1UL : 2UL);
            var cd = s / 210 * 3 + si; var cm = (ushort)(sm >> (int)(si << 4));
            for (ulong c = cd; c < nwrds; c += pt3) { //tight culling loop for size of master pattern
              this.buf[c] |= cm; // ++this.opcnt; //reverse logic; mark composites with ones.
            }
          }
        }
        //Now copy the master pattern so it repeats across the main buffer, allow for overflow...
        for (long i = 138567; i < this.buf.Length; i += 138567)
          if (i + 138567 <= this.buf.Length)
            Array.Copy(this.buf, 0, this.buf, i, 138567);
          else Array.Copy(this.buf, 0, this.buf, i, this.buf.Length - i);

        //Eliminate all composites which are factors of base primes, only for those on the wheel:
        for (uint i = 0, w = 0, wi = 0, pd = 0, pn = 0, msk = 1; w < this.buf.Length; ++i) {
          uint p = pd + modPRMS[pn];
          ulong sqr = (ulong)p * (ulong)p;
          if (sqr > range) break;
          if ((this.buf[w] & msk) == 0) { //found base prime, mark its composites...
            ulong s = sqr - 23; ulong pt3 = p * 3;
            for (int j = 0; s <= nrng && j < modPRMS.Length; s += p * gapsPRMS[(pn + j++) % 48]) {
              var sm = modLUT[s % 210];
              var si = (sm < (1UL << 16)) ? 0UL : ((sm < (1UL << 32)) ? 1UL : 2UL);
              var cd = s / 210 * 3 + si; var cm = (ushort)(sm >> (int)(si << 4));
              for (ulong c = cd; c < (ulong)this.buf.Length; c += pt3) { //tight culling loop
                this.buf[c] |= cm; // ++this.opcnt; //reverse logic; mark composites with ones.
              }
            }
          }
          ++pn;
          if (msk >= 0x8000) { msk = 1; ++w; ++wi; if (wi == 3) { wi = 0; pn = 0; pd += 210; } }
          else msk <<= 1;
        }

        //clear any overflow primes in the excess space in the last wheel/word:
        var ndx = nrng % 210; //clear any primes beyond the range
        for (; modLUT[ndx] == 0; --ndx) ;
        var cmsk = (~(modLUT[ndx] - 1)) << 1; //force all bits above to be composite ones.
        this.buf[lmtwt3++] |= (ushort)cmsk;
        this.buf[lmtwt3++] |= (ushort)(cmsk >> 16);
        this.buf[lmtwt3] |= (ushort)(cmsk >> 32);
      }
    }

    //uses a fast pop count Look Up Table to return the total number of primes...
    public long Count {
      get {
        long cnt = this.cnt;
        for (int i = 0; i < this.buf.Length; ++i) cnt += cntLUT[this.buf[i]];
        return cnt;
      }
    }

    //returns the number of cull operations used to sieve the prime bit array...
    public long Ops {
      get {
        return this.opcnt;
      }
    }

    //generate the enumeration of primes...
    public IEnumerator<ulong> GetEnumerator() {
      yield return 2; yield return 3; yield return 5; yield return 7;
      yield return 11; yield return 13; yield return 17; yield return 19;
      ulong pd = 0;
      for (uint i = 0, w = 0, wi = 0, pn = 0, msk = 1; w < this.buf.Length; ++i) {
        if ((this.buf[w] & msk) == 0) //found a prime bit...
          yield return pd + modPRMS[pn];
        ++pn;
        if (msk >= 0x8000) { msk = 1; ++w; ++wi; if (wi == 3) { wi = 0; pn = 0; pd += 210; } }
        else msk <<= 1;
      }
    }

    //required for the above enumeration...
    IEnumerator IEnumerable.GetEnumerator() {
      return this.GetEnumerator();
    }
  }

  class Program {
    static void Main(string[] args) {
      Console.WriteLine("This program calculates primes by a simple maximually wheel factorized version of the Sieve of Eratosthenes.\r\n");

      const ulong n = 1000000000;

      var elpsd = -DateTime.Now.Ticks;

      var gen = new SoE(n);

      elpsd += DateTime.Now.Ticks;

      Console.WriteLine("{0} primes found to {1} using {2} operations in {3} milliseconds.", gen.Count, n, gen.Ops, elpsd / 10000);

//      Console.WriteLine();
//      foreach (var p in gen) {
//        Console.Write(p + " ");
//      }
//      Console.WriteLine();

      //      Console.WriteLine("\r\nThere are {0} primes with the last one {1} and the sum {2}.",gen.Count(),gen.Last(),gen.Sum(x => (long)x));

      Console.Write("\r\nPress any key to exit:");
      Console.ReadKey(true);
      Console.WriteLine();
    }
  }
}

Этот код на самом деле работает на несколько процентов быстрее, чем приведенный выше SoA, как и должно быть, поскольку операций немного меньше, а основным узким местом для этого большого размера массива в диапазоне от миллиарда является время доступа к памяти от примерно 40 до более 100 тактовых циклов ЦП. в зависимости от характеристик процессора и памяти; это означает, что оптимизация кода (кроме уменьшения общего количества операций) неэффективна, поскольку большую часть времени тратится на ожидание доступа к памяти. В любом случае, использование огромного буфера памяти - не самый эффективный способ отсеивания больших диапазонов, с почти восьмикратным улучшением SoE с использованием сегментации страниц с той же максимальной факторизацией колеса (что также открывает путь для мультиобработка).

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

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

Когда-нибудь я адаптирую эти методы к многопоточному сегментированному Сито Эратосфена, чтобы оно было примерно таким же быстрым в C #, как "primegen" реализация SoA в 'C' Аткина и Бернштейна, и вынесу его из воды для больших диапазонов более четырех миллиардов, даже однопоточных, с дополнительным увеличением скорости примерно до четырех при многопоточности на моем i7 (восемь ядер, включая Hyper Threading).

person GordonBGood    schedule 28.04.2014
comment
Престижность за тщательный анализ и исключительное качество вашей статьи; Престижность также за признание - после всей этой тяжелой работы - что Решето Аткина на самом деле непрактично, поскольку Решето Эратосфена всегда быстрее на практике при тех же усилиях по реализации. Однако несегментированные сита - это отвлекающий маневр, поскольку сегментированные сита работают намного быстрее. Чтобы получить реальную скорость от Решета Эратосфена, разверните полное колесо в теле цикла (чтобы получить постоянные множители индексов и позиции битов). Это означает одно тело цикла для каждого из 8 классов остатка по модулю 30. - person DarthGizka; 03.05.2016
comment
@DarthGizka, спасибо, вам может быть интересна моя статья о многопоточности C #: stackoverflow.com/a/18885065/549617, где Я продвигаю сегментированную SoE немного дальше. Я работаю над многопоточной версией, которая использует разворачивание остатков (на самом деле 48 мод 210) и предварительную отбраковку, чтобы получить скорость примерно в два-четыре раза быстрее, чем в этой статье (в зависимости от процессора), К сожалению, я не нашел времени, чтобы закончить статья Но довольно скоро. Он идет еще дальше, чтобы показать, что на практике SoE всегда быстрее, чем SoA, когда используется максимальная колесная факторизация, особенно при сегментации. - person GordonBGood; 04.05.2016
comment
Спасибо, это конечно выглядит очень интересно! Я еще не дошел до более высоких заказов колес, потому что я хочу генерировать IL напрямую (во время выполнения) вместо использования генерации исходного кода, как в C ++ и FoxPro. Когда в Риме ... и все такое. Угол IL исходит из некоторых исследований, которые я провожу для портирования программы FoxPro на C #, которая включает интерпретацию формул, извлеченных из криптонов записей базы данных. В C # это означает либо деревья выражений, либо IL, но на самом деле соперником является только IL, поскольку порт должен увеличивать пропускную способность, а не уменьшать ее ... ;-) - person DarthGizka; 04.05.2016
comment
@DarthGizka, я поменял окончательный код на SoE из SoA. Как уже отмечалось, для этих больших диапазонов просеивания, составляющих миллиард, огромный массив буферов просеивания (даже с битовой упаковкой и уменьшенным коэффициентом колеса) намного больше, чем кеш-память ЦП, и, таким образом, этот алгоритм ограничен временем доступа к основной памяти, которое может быть больше 100 тактов ЦП, тогда как время цикла основной операции отсечения может быть значительно меньше 10 (всего около 5,5 для некоторых ЦП), если размер буфера умещается в кеш-памяти ЦП. Таким образом, отсев до миллиарда должен занять гораздо меньше секунды на современном процессоре для сегментированной версии. - person GordonBGood; 05.05.2016
comment
@DarthGizka, обратите внимание, что вместо того, чтобы иметь отдельные циклы для каждого остатка по модулю, для каждого базового простого числа я вычисляю начальные адреса и битовые смещения для каждого остатка в цикле (цикл 48 раз для моей внутренней факторизации колеса 2/3/5/7) и запустите цикл строгой отбраковки внутри этого внешнего цикла, который, в свою очередь, находится внутри цикла через определение базового прайма. Это требует немного времени при вычислении начального адреса на остаток, но это время незначительно, так как это выполняется только один раз на базовое простое число на остаток для этого большого алгоритма массива - от 162 864 вычислений до примерно 251 000 000 операций для диапазона в миллиард. - person GordonBGood; 05.05.2016
comment
@DarthGizka, вы правы в том, что единственная практическая реализация для очень больших диапазонов просеивания - это использование сегментации страниц, при этом сегментированный SoE намного проще реализовать и работает быстрее для больших диапазонов, чем сегментированный SoA. Помимо медлительности из-за узкого места доступа к основной памяти, эти реализации имеют предел диапазона просеивания около 60 миллиардов для версии SoA и 75 миллиардов для версии SoE, причем эти ограничения примерно удваиваются, если включена опция времени выполнения gcAllowVeryLargeObjects. - person GordonBGood; 05.05.2016

Вот еще одна реализация. Он использует BitArray для экономии памяти. Parallel.For требуется .NET Framework 4.

static List<int> FindPrimesBySieveOfAtkins(int max)
{
//  var isPrime = new BitArray((int)max+1, false); 
//  Can't use BitArray because of threading issues.
    var isPrime = new bool[max + 1];
    var sqrt = (int)Math.Sqrt(max);

    Parallel.For(1, sqrt, x =>
    {
        var xx = x * x;
        for (int y = 1; y <= sqrt; y++)
        {
            var yy = y * y;
            var n = 4 * xx + yy;
            if (n <= max && (n % 12 == 1 || n % 12 == 5))
                isPrime[n] ^= true;

            n = 3 * xx + yy;
            if (n <= max && n % 12 == 7)
                isPrime[n] ^= true;

            n = 3 * xx - yy;
            if (x > y && n <= max && n % 12 == 11)
                isPrime[n] ^= true;
        }
    });

    var primes = new List<int>() { 2, 3 };
    for (int n = 5; n <= sqrt; n++)
    {
        if (isPrime[n])
        {
            primes.Add(n);
            int nn = n * n;
            for (int k = nn; k <= max; k += nn)
                isPrime[k] = false;
        }
    }

    for (int n = sqrt + 1; n <= max; n++)
        if (isPrime[n])
            primes.Add(n);

    return primes;
}
person Jonas Elfström    schedule 15.01.2010
comment
На первый взгляд это выглядит действительно круто и действительно довольно быстро, но похоже, что это не работает должным образом. Попробуйте FindPrimesBySieveOfAtkins (1000000) .Count, и вы получите разные значения около отметки 78500. Предположительно это связано с параллелизмом, как я уверен, вы можете видеть. - person Tom Chantler; 14.12.2010
comment
Вы абсолютно правы. Я беспокоился о характеристиках BitArray, не связанных с потоками, но я думал, что isPrime[n] ^= true; была атомарной операцией и что неважно, в каком порядке биты биты были перевернуты, можно было бы использовать в любом случае. Не так. Изменил его на логический массив, и это, кажется, раскачивает лодку, но, конечно, с гораздо большей стоимостью памяти. - person Jonas Elfström; 14.12.2010
comment
Можно ли было бы использовать битовый массив, если бы вы использовали msdn .microsoft.com / en-us / library / класс или что-то в этом роде? - person Svish; 17.11.2011
comment
isPrime [n] ^ = истина; не является потокобезопасным. Простой способ проверить это (но вы также можете сделать это с помощью bool XOR): int i = 0; Parallel.For (0, 10000, (x) = ›{i + = 1;}); Console.WriteLine (i); - person oddbear; 18.12.2014

Вот более быстрая реализация Решета Аткина, я украл алгоритм из этого скрипта Python здесь (я не беру на себя ответственность за алгоритм):

http://programmingpraxis.com/2010/02/19/sieve-of-atkin-improved/

using System;
using System.Collections;
using System.Collections.Generic;

namespace PrimeGenerator
{
    // The block element type for the bit array, 
    // use any unsigned value. WARNING: UInt64 is 
    // slower even on x64 architectures.
    using BitArrayType = System.UInt32;

    // This should never be any bigger than 256 bits - leave as is.
    using BitsPerBlockType = System.Byte;

    // The prime data type, this can be any unsigned value, the limit
    // of this type determines the limit of Prime value that can be
    // found. WARNING: UInt64 is slower even on x64 architectures.
    using PrimeType = System.Int32;

    /// <summary>
    /// Calculates prime number using the Sieve of Eratosthenes method.
    /// </summary>
    /// <example>
    /// <code>
    ///     var lpPrimes = new Eratosthenes(1e7);
    ///     foreach (UInt32 luiPrime in lpPrimes)
    ///         Console.WriteLine(luiPrime);
    /// </example>
    public class Atkin : IEnumerable<PrimeType>
    {
        #region Constants

        /// <summary>
        /// Constant for number of bits per block, calculated based on size of BitArrayType.
        /// </summary>
        const BitsPerBlockType cbBitsPerBlock = sizeof(BitArrayType) * 8;

        #endregion

        #region Protected Locals

        /// <summary>
        /// The limit for the maximum prime value to find.
        /// </summary>
        protected readonly PrimeType mpLimit;

        /// <summary>
        /// The number of primes calculated or null if not calculated yet.
        /// </summary>
        protected PrimeType? mpCount = null;

        /// <summary>
        /// The current bit array where a set bit means
        /// the odd value at that location has been determined
        /// to not be prime.
        /// </summary>
        protected BitArrayType[] mbaOddPrime;

        #endregion

        #region Initialisation

        /// <summary>
        /// Create Sieve of Atkin generator.
        /// </summary>
        /// <param name="limit">The limit for the maximum prime value to find.</param>
        public Atkin(PrimeType limit)
        {
            // Check limit range
            if (limit > PrimeType.MaxValue - (PrimeType)Math.Sqrt(PrimeType.MaxValue))
                throw new ArgumentOutOfRangeException();

            mpLimit = limit;

            FindPrimes();
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Finds the prime number within range.
        /// </summary>
        private unsafe void FindPrimes()
        {
            // Allocate bit array.
            mbaOddPrime = new BitArrayType[(((mpLimit >> 1) + 1) / cbBitsPerBlock) + 1];

            PrimeType lpYLimit, lpN, lpXX3, lpXX4, lpDXX, lpDN, lpDXX4, lpXX, lpX, lpYY, lpMinY, lpS, lpK;

            fixed (BitArrayType* lpbOddPrime = &mbaOddPrime[0])
            {
                // n = 3x^2 + y^2 section
                lpXX3 = 3;
                for (lpDXX = 0; lpDXX < 12 * SQRT((mpLimit - 1) / 3); lpDXX += 24)
                {
                    lpXX3 += lpDXX;
                    lpYLimit = (12 * SQRT(mpLimit - lpXX3)) - 36;
                    lpN = lpXX3 + 16;

                    for (lpDN = -12; lpDN < lpYLimit + 1; lpDN += 72)
                    {
                        lpN += lpDN;
                        lpbOddPrime[(lpN >> 1) / cbBitsPerBlock] ^= 
                            (BitArrayType)((BitArrayType)1 << (int)((lpN >> 1) % cbBitsPerBlock));
                    }

                    lpN = lpXX3 + 4;
                    for (lpDN = 12; lpDN < lpYLimit + 1; lpDN += 72)
                    {
                        lpN += lpDN;
                        lpbOddPrime[(lpN >> 1) / cbBitsPerBlock] ^= 
                            (BitArrayType)((BitArrayType)1 << (int)((lpN >> 1) % cbBitsPerBlock));
                    }
                }

                //    # n = 4x^2 + y^2 section
                lpXX4 = 0;
                for (lpDXX4 = 4; lpDXX4 < 8 * SQRT((mpLimit - 1) / 4) + 4; lpDXX4 += 8)
                {
                    lpXX4 += lpDXX4;
                    lpN = lpXX4 + 1;

                    if ((lpXX4 % 3) != 0)
                    {
                        for (lpDN = 0; lpDN < (4 * SQRT(mpLimit - lpXX4)) - 3; lpDN += 8)
                        {
                            lpN += lpDN;
                            lpbOddPrime[(lpN >> 1) / cbBitsPerBlock] ^= 
                                (BitArrayType)((BitArrayType)1 << (int)((lpN >> 1) % cbBitsPerBlock));
                        }
                    }
                    else
                    {
                        lpYLimit = (12 * SQRT(mpLimit - lpXX4)) - 36;
                        lpN = lpXX4 + 25;

                        for (lpDN = -24; lpDN < lpYLimit + 1; lpDN += 72)
                        {
                            lpN += lpDN;
                            lpbOddPrime[(lpN >> 1) / cbBitsPerBlock] ^= 
                                (BitArrayType)((BitArrayType)1 << (int)((lpN >> 1) % cbBitsPerBlock));
                        }

                        lpN = lpXX4 + 1;
                        for (lpDN = 24; lpDN < lpYLimit + 1; lpDN += 72)
                        {
                            lpN += lpDN;
                            lpbOddPrime[(lpN >> 1) / cbBitsPerBlock] ^= 
                                (BitArrayType)((BitArrayType)1 << (int)((lpN >> 1) % cbBitsPerBlock));
                        }
                    }
                }

                //    # n = 3x^2 - y^2 section
                lpXX = 1;
                for (lpX = 3; lpX < SQRT(mpLimit / 2) + 1; lpX += 2)
                {
                    lpXX += 4 * lpX - 4;
                    lpN = 3 * lpXX;

                    if (lpN > mpLimit)
                    {
                        lpMinY = ((SQRT(lpN - mpLimit) >> 2) << 2);
                        lpYY = lpMinY * lpMinY;
                        lpN -= lpYY;
                        lpS = 4 * lpMinY + 4;
                    }
                    else
                        lpS = 4;

                    for (lpDN = lpS; lpDN < 4 * lpX; lpDN += 8)
                    {
                        lpN -= lpDN;
                        if (lpN <= mpLimit && lpN % 12 == 11)
                            lpbOddPrime[(lpN >> 1) / cbBitsPerBlock] ^= 
                                (BitArrayType)((BitArrayType)1 << (int)((lpN >> 1) % cbBitsPerBlock));
                    }
                }

                // xx = 0
                lpXX = 0;
                for (lpX = 2; lpX < SQRT(mpLimit / 2) + 1; lpX += 2)
                {
                    lpXX += 4*lpX - 4;
                    lpN = 3*lpXX;

                    if (lpN > mpLimit)
                    {
                        lpMinY = ((SQRT(lpN - mpLimit) >> 2) << 2) - 1;
                        lpYY = lpMinY * lpMinY;
                        lpN -= lpYY;
                        lpS = 4*lpMinY + 4;
                    }
                    else
                    {
                        lpN -= 1;
                        lpS = 0;
                    }

                    for (lpDN = lpS; lpDN < 4 * lpX; lpDN += 8)
                    {
                        lpN -= lpDN;
                        if (lpN <= mpLimit && lpN % 12 == 11)
                            lpbOddPrime[(lpN>>1) / cbBitsPerBlock] ^= 
                                (BitArrayType)((BitArrayType)1 << (int)((lpN>>1) % cbBitsPerBlock));
                    }
                }

                // # eliminate squares
                for (lpN = 5; lpN < SQRT(mpLimit) + 1; lpN += 2)
                    if ((lpbOddPrime[(lpN >> 1) / cbBitsPerBlock] & ((BitArrayType)1 << (int)((lpN >> 1) % cbBitsPerBlock))) != 0)
                        for (lpK = lpN * lpN; lpK < mpLimit; lpK += lpN * lpN)
                            if ((lpK & 1) == 1)
                                lpbOddPrime[(lpK >> 1) / cbBitsPerBlock] &=
                                    (BitArrayType)~((BitArrayType)1 << (int)((lpK >> 1) % cbBitsPerBlock));
            }
        }

        /// <summary>
        /// Calculates the truncated square root for a number.
        /// </summary>
        /// <param name="value">The value to get the square root for.</param>
        /// <returns>The truncated sqrt of the value.</returns>
        private unsafe PrimeType SQRT(PrimeType value)
        {
            return (PrimeType)Math.Sqrt(value);
        }

        /// <summary>
        /// Gets a bit value by index.
        /// </summary>
        /// <param name="bits">The blocks containing the bits.</param>
        /// <param name="index">The index of the bit.</param>
        /// <returns>True if bit is set, false if cleared.</returns>
        private bool GetBitSafe(BitArrayType[] bits, PrimeType index)
        {
            if ((index & 1) == 1)
                return (bits[(index >> 1) / cbBitsPerBlock] & ((BitArrayType)1 << (int)((index >> 1) % cbBitsPerBlock))) != 0;
            else
                return false;
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Get the limit for the maximum prime value to find.
        /// </summary>
        public PrimeType Limit
        {
            get
            {
                return mpLimit;
            }
        }

        /// <summary>
        /// Returns the number of primes found in the range.
        /// </summary>
        public PrimeType Count
        {
            get
            {
                if (!mpCount.HasValue)
                {
                    PrimeType lpCount = 0;
                    foreach (PrimeType liPrime in this) lpCount++;
                    mpCount = lpCount;
                }

                return mpCount.Value;
            }
        }

        /// <summary>
        /// Determines if a value in range is prime or not.
        /// </summary>
        /// <param name="test">The value to test for primality.</param>
        /// <returns>True if the value is prime, false otherwise.</returns>
        public bool this[PrimeType test]
        {
            get
            {
                if (test > mpLimit) throw new ArgumentOutOfRangeException();
                if (test <= 1) return false;
                if (test == 2) return true;
                if ((test & 1) == 0) return false;
                return !GetBitSafe(mbaOddPrime, test >> 1);
            }
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Gets the enumerator for the primes.
        /// </summary>
        /// <returns>The enumerator of the primes.</returns>
        public IEnumerator<PrimeType> GetEnumerator()
        {
            //    return [2,3] + filter(primes.__getitem__, xrange(5,limit,2))

            // Two & Three always prime.
            yield return 2;
            yield return 3;

            // Start at first block, third MSB (5).
            int liBlock = 0;
            byte lbBit = 2;
            BitArrayType lbaCurrent = mbaOddPrime[0] >> lbBit;

            // For each value in range stepping in incrments of two for odd values.
            for (PrimeType lpN = 5; lpN <= mpLimit; lpN += 2)
            {
                // If current bit not set then value is prime.
                if ((lbaCurrent & 1) == 1)
                    yield return lpN;

                // Move to NSB.
                lbaCurrent >>= 1;

                // Increment bit value. 
                lbBit++;

                // If block is finished.
                if (lbBit == cbBitsPerBlock) 
                {
                    lbBit = 0;
                    lbaCurrent = mbaOddPrime[++liBlock];

                    //// Move to first bit of next block skipping full blocks.
                    while (lbaCurrent == 0)
                    {
                        lpN += ((PrimeType)cbBitsPerBlock) << 1;
                        if (lpN <= mpLimit)
                            lbaCurrent = mbaOddPrime[++liBlock];
                        else
                            break;
                    }
                }
            }
        }

        #endregion

        #region IEnumerable<PrimeType> Implementation

        /// <summary>
        /// Gets the enumerator for the primes.
        /// </summary>
        /// <returns></returns>
        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        #endregion
    }
}

Он близок по скорости к моей наиболее оптимизированной версии Сита Эратосфена, но все еще медленнее примерно на 20%, его можно найти здесь:

https://stackoverflow.com/a/9700790/738380

person Aaron Murgatroyd    schedule 15.03.2012
comment
обработка большого массива кусками, как вы это делаете для многопоточной версии SoE, скорее всего, заставит это работать быстрее, чем эквивалентная версия вашей реализации SoE, поскольку это уменьшит перегрузку кеш-памяти при доступе к памяти. Однако, если к вашей SoE удаляются довольно высокие коэффициенты с использованием колесной факторизации, SoE снова будет опережать SoA для любого диапазона чисел, в котором нам нужно подождать (т.е. меньше дней), потому что количество составных отбраковок SoE будет тогда будет меньше, чем количество переключателей этой SoA. - person GordonBGood; 11.10.2013
comment
Эталонная реализация SoA Берштейном и Аткином по сравнению с эквивалентной реализацией SoE использовала только 2,3,5-колесную факторизацию для SoE, потому что это эквивалентно собственной факторизации колес SoA, но с гораздо большими факторами, такими как 2,3, 5,7,11,13 возможны для SoE, тогда как SoA не реагирует на дальнейшую факторизацию колеса. Таким образом, количество составных отбраковок с помощью SoE может быть сокращено примерно до двух третей от количества переключений с помощью SoA, чтобы, вероятно, немного опередить SoE по сравнению с еще более оптимизированной SoA, несмотря на дополнительную сложность. - person GordonBGood; 11.10.2013
comment
Следует задать вопрос: зачем использовать сито Аткина, а не сито Эратосфена, когда оба максимально оптимизированы? и ответ: Скорее всего, нет никакой причины., поскольку я разрабатываю в этом ответе. Не поэтому ваш код SoA здесь на 20% медленнее, что, скорее всего, связано с тем, что этот алгоритм все еще не полностью устранил необходимость в модуле для одного из квадратичных случаев, и ваша реализация SoE не оптимизирована максимально. по мере развития в моем многопоточном ответе. - person GordonBGood; 01.01.2014

Вот мой, он использует класс под названием CompartmentalisedParallel, который позволяет выполнять параллельные циклы, но контролировать количество потоков, чтобы индексы были сгруппированы. Однако из-за проблем с потоковой передачей вам нужно либо блокировать BitArray каждый раз, когда он изменяется, либо создавать отдельный BitArray для каждого потока, а затем выполнять XOR их вместе в конце, первый вариант был довольно медленным из-за количества блокировок, второй вариант мне показался более быстрым!

using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace PrimeGenerator
{
    public class Atkin : Primes
    {
        protected BitArray mbaPrimes;
        protected bool mbThreaded = true;

        public Atkin(int limit)
            : this(limit, true)
        {
        }

        public Atkin(int limit, bool threaded)
            : base(limit)
        {
            mbThreaded = threaded;
            if (mbaPrimes == null) FindPrimes();
        }

        public bool Threaded
        {
            get
            {
                return mbThreaded;
            }
        }

        public override IEnumerator<int> GetEnumerator()
        {
            yield return 2;
            yield return 3;
            for (int lsN = 5; lsN <= msLimit; lsN += 2)
                if (mbaPrimes[lsN]) yield return lsN;
        }

        private void FindPrimes()
        {
            mbaPrimes = new BitArray(msLimit + 1, false);

            int lsSQRT = (int)Math.Sqrt(msLimit);

            int[] lsSquares = new int[lsSQRT + 1];
            for (int lsN = 0; lsN <= lsSQRT; lsN++)
                lsSquares[lsN] = lsN * lsN;

            if (Threaded)
            {
                CompartmentalisedParallel.For<BitArray>(
                    1, lsSQRT + 1, new ParallelOptions(),
                    (start, finish) => { return new BitArray(msLimit + 1, false); },
                    (lsX, lsState, lbaLocal) =>
                    {
                        int lsX2 = lsSquares[lsX];

                        for (int lsY = 1; lsY <= lsSQRT; lsY++)
                        {
                            int lsY2 = lsSquares[lsY];

                            int lsN = 4 * lsX2 + lsY2;
                            if (lsN <= msLimit && (lsN % 12 == 1 || lsN % 12 == 5))
                                lbaLocal[lsN] ^= true;

                            lsN -= lsX2;
                            if (lsN <= msLimit && lsN % 12 == 7)
                                lbaLocal[lsN] ^= true;

                            if (lsX > lsY)
                            {
                                lsN -= lsY2 * 2;
                                if (lsN <= msLimit && lsN % 12 == 11)
                                    lbaLocal[lsN] ^= true;
                            }
                        }

                        return lbaLocal;
                    },
                    (lbaResult, start, finish) =>
                    {
                        lock (mbaPrimes) 
                            mbaPrimes.Xor(lbaResult);
                    },
                    -1
                );
            }
            else
            {
                for (int lsX = 1; lsX <= lsSQRT; lsX++)
                {
                    int lsX2 = lsSquares[lsX];

                    for (int lsY = 1; lsY <= lsSQRT; lsY++)
                    {
                        int lsY2 = lsSquares[lsY];

                        int lsN = 4 * lsX2 + lsY2;
                        if (lsN <= msLimit && (lsN % 12 == 1 || lsN % 12 == 5))
                            mbaPrimes[lsN] ^= true;

                        lsN -= lsX2;
                        if (lsN <= msLimit && lsN % 12 == 7)
                            mbaPrimes[lsN] ^= true;

                        if (lsX > lsY)
                        {
                            lsN -= lsY2 * 2;
                            if (lsN <= msLimit && lsN % 12 == 11)
                                mbaPrimes[lsN] ^= true;
                        }
                    }
                }
            }

            for (int lsN = 5; lsN < lsSQRT; lsN += 2)
                if (mbaPrimes[lsN])
                {
                    var lsS = lsSquares[lsN];
                    for (int lsK = lsS; lsK <= msLimit; lsK += lsS)
                        mbaPrimes[lsK] = false;
                }
        }
    }
}

И класс CompartmentalisedParallel:

using System;
using System.Threading.Tasks;

namespace PrimeGenerator
{
    public static class CompartmentalisedParallel
    {
        #region Int

        private static int[] CalculateCompartments(int startInclusive, int endExclusive, ref int threads)
        {
            if (threads == 0) threads = 1;
            if (threads == -1) threads = Environment.ProcessorCount;
            if (threads > endExclusive - startInclusive) threads = endExclusive - startInclusive;

            int[] liThreadIndexes = new int[threads + 1];
            liThreadIndexes[threads] = endExclusive - 1;
            int liIndexesPerThread = (endExclusive - startInclusive) / threads;
            for (int liCount = 0; liCount < threads; liCount++)
                liThreadIndexes[liCount] = liCount * liIndexesPerThread;

            return liThreadIndexes;
        }

        public static void For<TLocal>(
            int startInclusive, int endExclusive,
            ParallelOptions parallelOptions,
            Func<int, int, TLocal> localInit,
            Func<int, ParallelLoopState, TLocal, TLocal> body,
            Action<TLocal, int, int> localFinally,
            int threads)
        {
            int[] liThreadIndexes = CalculateCompartments(startInclusive, endExclusive, ref threads);

            if (threads > 1)
                Parallel.For(
                    0, threads, parallelOptions,
                    (liThread, lsState) =>
                    {
                        TLocal llLocal = localInit(liThreadIndexes[liThread], liThreadIndexes[liThread + 1]);

                        for (int liCounter = liThreadIndexes[liThread]; liCounter < liThreadIndexes[liThread + 1]; liCounter++)
                            body(liCounter, lsState, llLocal);

                        localFinally(llLocal, liThreadIndexes[liThread], liThreadIndexes[liThread + 1]);
                    }
                );
            else
            {
                TLocal llLocal = localInit(startInclusive, endExclusive);
                for (int liCounter = startInclusive; liCounter < endExclusive; liCounter++)
                    body(liCounter, null, llLocal);
                localFinally(llLocal, startInclusive, endExclusive);
            }
        }

        public static void For(
            int startInclusive, int endExclusive,
            ParallelOptions parallelOptions,
            Action<int, ParallelLoopState> body,
            int threads)
        {
            int[] liThreadIndexes = CalculateCompartments(startInclusive, endExclusive, ref threads);

            if (threads > 1)
                Parallel.For(
                    0, threads, parallelOptions,
                    (liThread, lsState) =>
                    {
                        for (int liCounter = liThreadIndexes[liThread]; liCounter < liThreadIndexes[liThread + 1]; liCounter++)
                            body(liCounter, lsState);
                    }
                );
            else
                for (int liCounter = startInclusive; liCounter < endExclusive; liCounter++)
                    body(liCounter, null);
        }

        public static void For(
            int startInclusive, int endExclusive,
            ParallelOptions parallelOptions,
            Action<int> body,
            int threads)
        {
            int[] liThreadIndexes = CalculateCompartments(startInclusive, endExclusive, ref threads);

            if (threads > 1)
                Parallel.For(
                    0, threads, parallelOptions,
                    (liThread) =>
                    {
                        for (int liCounter = liThreadIndexes[liThread]; liCounter < liThreadIndexes[liThread + 1]; liCounter++)
                            body(liCounter);
                    }
                );
            else
                for (int liCounter = startInclusive; liCounter < endExclusive; liCounter++)
                    body(liCounter);
        }

        public static void For(
            int startInclusive, int endExclusive,
            Action<int, ParallelLoopState> body,
            int threads)
        {
            For(startInclusive, endExclusive, new ParallelOptions(), body, threads);
        }

        public static void For(
            int startInclusive, int endExclusive,
            Action<int> body,
            int threads)
        {
            For(startInclusive, endExclusive, new ParallelOptions(), body, threads);
        }

        public static void For<TLocal>(
            int startInclusive, int endExclusive,
            Func<int, int, TLocal> localInit,
            Func<int, ParallelLoopState, TLocal, TLocal> body,
            Action<TLocal, int, int> localFinally,
            int threads)
        {
            For<TLocal>(startInclusive, endExclusive, new ParallelOptions(), localInit, body, localFinally, threads);
        }

        #endregion

        #region Long

        private static long[] CalculateCompartments(long startInclusive, long endExclusive, ref long threads)
        {
            if (threads == 0) threads = 1;
            if (threads == -1) threads = Environment.ProcessorCount;
            if (threads > endExclusive - startInclusive) threads = endExclusive - startInclusive;

            long[] liThreadIndexes = new long[threads + 1];
            liThreadIndexes[threads] = endExclusive - 1;
            long liIndexesPerThread = (endExclusive - startInclusive) / threads;
            for (long liCount = 0; liCount < threads; liCount++)
                liThreadIndexes[liCount] = liCount * liIndexesPerThread;

            return liThreadIndexes;
        }

        public static void For<TLocal>(
            long startInclusive, long endExclusive,
            ParallelOptions parallelOptions,
            Func<long, long, TLocal> localInit,
            Func<long, ParallelLoopState, TLocal, TLocal> body,
            Action<TLocal, long, long> localFinally,
            long threads)
        {
            long[] liThreadIndexes = CalculateCompartments(startInclusive, endExclusive, ref threads);

            if (threads > 1)
                Parallel.For(
                    0, threads, parallelOptions,
                    (liThread, lsState) =>
                    {
                        TLocal llLocal = localInit(liThreadIndexes[liThread], liThreadIndexes[liThread + 1]);

                        for (long liCounter = liThreadIndexes[liThread]; liCounter < liThreadIndexes[liThread + 1]; liCounter++)
                            body(liCounter, lsState, llLocal);

                        localFinally(llLocal, liThreadIndexes[liThread], liThreadIndexes[liThread + 1]);
                    }
                );
            else
            {
                TLocal llLocal = localInit(startInclusive, endExclusive);
                for (long liCounter = startInclusive; liCounter < endExclusive; liCounter++)
                    body(liCounter, null, llLocal);
                localFinally(llLocal, startInclusive, endExclusive);
            }
        }

        public static void For(
            long startInclusive, long endExclusive,
            ParallelOptions parallelOptions,
            Action<long, ParallelLoopState> body,
            long threads)
        {
            long[] liThreadIndexes = CalculateCompartments(startInclusive, endExclusive, ref threads);

            if (threads > 1)
                Parallel.For(
                    0, threads, parallelOptions,
                    (liThread, lsState) =>
                    {
                        for (long liCounter = liThreadIndexes[liThread]; liCounter < liThreadIndexes[liThread + 1]; liCounter++)
                            body(liCounter, lsState);
                    }
                );
            else
                for (long liCounter = startInclusive; liCounter < endExclusive; liCounter++)
                    body(liCounter, null);
        }

        public static void For(
            long startInclusive, long endExclusive,
            ParallelOptions parallelOptions,
            Action<long> body,
            long threads)
        {
            long[] liThreadIndexes = CalculateCompartments(startInclusive, endExclusive, ref threads);

            if (threads > 1)
                Parallel.For(
                    0, threads, parallelOptions,
                    (liThread) =>
                    {
                        for (long liCounter = liThreadIndexes[liThread]; liCounter < liThreadIndexes[liThread + 1]; liCounter++)
                            body(liCounter);
                    }
                );
            else
                for (long liCounter = startInclusive; liCounter < endExclusive; liCounter++)
                    body(liCounter);
        }

        public static void For(
            long startInclusive, long endExclusive,
            Action<long, ParallelLoopState> body,
            long threads)
        {
            For(startInclusive, endExclusive, new ParallelOptions(), body, threads);
        }

        public static void For(
            long startInclusive, long endExclusive,
            Action<long> body,
            long threads)
        {
            For(startInclusive, endExclusive, new ParallelOptions(), body, threads);
        }

        public static void For<TLocal>(
            long startInclusive, long endExclusive,
            Func<long, long, TLocal> localInit,
            Func<long, ParallelLoopState, TLocal, TLocal> body,
            Action<TLocal, long, long> localFinally,
            long threads)
        {
            For<TLocal>(startInclusive, endExclusive, new ParallelOptions(), localInit, body, localFinally, threads);
        }

        #endregion
    }
}

Базовый класс простых чисел:

using System.Collections;
using System.Collections.Generic;

namespace PrimeGenerator
{
    public abstract class Primes : IEnumerable<int>
    {
        protected readonly int msLimit;

        public Primes(int limit)
        {
            msLimit = limit;
        }

        public int Limit
        {
            get
            {
                return msLimit;
            }
        }

        public int Count
        {
            get
            {
                int liCount = 0;
                foreach (int liPrime in this)
                    liCount++;
                return liCount;
            }
        }

        public abstract IEnumerator<int> GetEnumerator();

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }
}

Используйте его, выполнив следующие действия:

    var lpPrimes = new Atkin(count, true);
    Console.WriteLine(lpPrimes.Count);
    Console.WriteLine(s.ElapsedMilliseconds);

Однако я обнаружил, что Eratosthenes был быстрее во всех случаях, даже с четырехъядерным процессором, работающим в многопоточном режиме для Atkin:

using System;
using System.Collections;
using System.Collections.Generic;

namespace PrimeGenerator
{
    public class Eratosthenes : Primes
    {
        protected BitArray mbaOddEliminated;

        public Eratosthenes(int limit)
            : base(limit)
        {
            if (mbaOddEliminated == null) FindPrimes();
        }

        public override IEnumerator<int> GetEnumerator()
        {
            yield return 2;
            for (int lsN = 3; lsN <= msLimit; lsN+=2)
                if (!mbaOddEliminated[lsN>>1]) yield return lsN;
        }

        private void FindPrimes()
        {
            mbaOddEliminated = new BitArray((msLimit>>1) + 1);
            int lsSQRT = (int)Math.Sqrt(msLimit);
            for (int lsN = 3; lsN < lsSQRT + 1; lsN += 2)
                if (!mbaOddEliminated[lsN>>1])
                    for (int lsM = lsN*lsN; lsM <= msLimit; lsM += lsN<<1)
                        mbaOddEliminated[lsM>>1] = true;
        }
    }
}

Если вы заставите Аткин работать быстрее, дайте мне знать!

person Aaron Murgatroyd    schedule 10.03.2012
comment
Вы можете заставить решето Аткина (SoA) работать быстрее несколькими способами: 1) Избегайте всей необходимости в (дорогостоящих) операциях по модулю, осознавая, что каждая из квадратичных последовательностей 4 * x ^ 2 + y ^ 2, 3 * x ^ 2 + y ^ 2 и 3 * x ^ 2-y ^ 2 следуют шаблону по модулю 15, чтобы генерировать только соответствующие числа по модулю для каждого следующего шаблона, чтобы быть быстрее более чем в 2 раза, 2) вы можете сегментируйте массивы отбраковки, чтобы не было проблем с параллелизмом, поскольку каждый поток имеет один (предпочтительно битовый) массив. Конечно, SoE также можно сегментировать и применять колесную факторизацию для дополнительного усиления. - person GordonBGood; 10.10.2013
comment
продолжение: В конце концов, SoA работает быстрее только тогда, когда SoE ограничивается факторизацией колеса с тем же исключением факторов 2, 3 и 5, на которых основана SoA; максимально оптимизированная SoE по-прежнему быстрее, чем максимально оптимизированная SoA для любых диапазонов простых чисел, которые мы, вероятно, захотим подождать. Это еще более верно, когда написано с использованием собственного языка компиляции, такого как C ++, поскольку более простые операции SoE более подходят для оптимизации компилятора, так что на составную отбраковку может потребоваться всего три тактовых цикла ЦП. Я не думаю, что SoA может быть настолько эффективным. - person GordonBGood; 10.10.2013
comment
продолжение: Я написал версию C #, которая перечисляет все 203 280 221 простых чисел до (четыре миллиарда плюс) примерно за 7,5 секунд на процессоре i7-2700K (3,5 ГГц) здесь еще один ответ, который использует сегментацию, многопоточность и факторизацию колеса. Более 2/3 этого времени перебирает найденные простые числа, поэтому алгоритм (SoE / SoA) не имеет большого значения, если оба оптимизированы. В этом ответе используется только факторизация колес 2,3,5, поэтому оптимизированная SoA должна быть немного быстрее, но если бы я использовал, скажем, факторизацию 2,3,5,7,11,13, тогда SoE снова будет быстрее. - person GordonBGood; 10.10.2013
comment
похоже, что ваш более поздний ответ в коде, переведенном с Python, улучшил проблемы оптимизации, которые я поднял в моем первом комментарии, но вы все еще не применили сегментацию и многопоточность к SoA ... - person GordonBGood; 11.10.2013

Это улучшение Решета Эратосфена с использованием настраиваемых FixBitArrays и небезопасного кода для определения скорости, это примерно на 225% быстрее, чем мой предыдущий алгоритм Эратосфена, и класс является автономным (это не многопоточный - Эратосфен не совместим с многопоточностью), На процессоре AMD Phenom II X4 965 я могу вычислить предельное число простых чисел до 1000000000 за 9261 мс:

using System;
using System.Collections;
using System.Collections.Generic;

namespace PrimeGenerator
{
    // The block element type for the bit array, 
    // use any unsigned value. WARNING: UInt64 is 
    // slower even on x64 architectures.
    using BitArrayType = System.UInt32;

    // This should never be any bigger than 256 bits - leave as is.
    using BitsPerBlockType = System.Byte;

    // The prime data type, this can be any unsigned value, the limit
    // of this type determines the limit of Prime value that can be
    // found. WARNING: UInt64 is slower even on x64 architectures.
    using PrimeType = System.UInt32;

    /// <summary>
    /// Calculates prime number using the Sieve of Eratosthenes method.
    /// </summary>
    /// <example>
    /// <code>
    ///     var lpPrimes = new Eratosthenes(1e7);
    ///     foreach (UInt32 luiPrime in lpPrimes)
    ///         Console.WriteLine(luiPrime);
    /// </example>
    public class Eratosthenes : IEnumerable<PrimeType>
    {
        #region Constants

        /// <summary>
        /// Constant for number of bits per block, calculated based on size of BitArrayType.
        /// </summary>
        const BitsPerBlockType cbBitsPerBlock = sizeof(BitArrayType) * 8;

        #endregion

        #region Protected Locals

        /// <summary>
        /// The limit for the maximum prime value to find.
        /// </summary>
        protected readonly PrimeType mpLimit;

        /// <summary>
        /// The current bit array where a set bit means
        /// the odd value at that location has been determined
        /// to not be prime.
        /// </summary>
        protected BitArrayType[] mbaOddNotPrime;

        #endregion

        #region Initialisation

        /// <summary>
        /// Create Sieve of Eratosthenes generator.
        /// </summary>
        /// <param name="limit">The limit for the maximum prime value to find.</param>
        public Eratosthenes(PrimeType limit)
        {
            // Check limit range
            if (limit > PrimeType.MaxValue - (PrimeType)Math.Sqrt(PrimeType.MaxValue))
                throw new ArgumentOutOfRangeException();

            mpLimit = limit;

            FindPrimes();
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Finds the prime number within range.
        /// </summary>
        private unsafe void FindPrimes()
        {
            // Allocate bit array.
            mbaOddNotPrime = new BitArrayType[(((mpLimit >> 1) + 1) / cbBitsPerBlock) + 1];

            // Cache Sqrt of limit.
            PrimeType lpSQRT = (PrimeType)Math.Sqrt(mpLimit);

            // Fix the bit array for pointer access
            fixed (BitArrayType* lpbOddNotPrime = &mbaOddNotPrime[0])
                // Scan primes up to lpSQRT
                for (PrimeType lpN = 3; lpN <= lpSQRT; lpN += 2)
                    // If the current bit value for index lpN is cleared (prime)
                    if (
                            (
                                lpbOddNotPrime[(lpN >> 1) / cbBitsPerBlock] & 
                                ((BitArrayType)1 << (BitsPerBlockType)((lpN >> 1) % cbBitsPerBlock))
                            ) == 0
                        )
                        // Leave it cleared (prime) and mark all multiples of lpN*2 from lpN*lpN as not prime
                        for (PrimeType lpM = lpN * lpN; lpM <= mpLimit; lpM += lpN << 1)
                            // Set as not prime
                            lpbOddNotPrime[(lpM >> 1) / cbBitsPerBlock] |= 
                                (BitArrayType)((BitArrayType)1 << (BitsPerBlockType)((lpM >> 1) % cbBitsPerBlock));
        }

        /// <summary>
        /// Gets a bit value by index.
        /// </summary>
        /// <param name="bits">The blocks containing the bits.</param>
        /// <param name="index">The index of the bit.</param>
        /// <returns>True if bit is set, false if cleared.</returns>
        private bool GetBitSafe(BitArrayType[] bits, PrimeType index)
        {
            return (bits[index / cbBitsPerBlock] & ((BitArrayType)1 << (BitsPerBlockType)(index % cbBitsPerBlock))) != 0;
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Get the limit for the maximum prime value to find.
        /// </summary>
        public PrimeType Limit
        {
            get
            {
                return mpLimit;
            }
        }

        /// <summary>
        /// Returns the number of primes found in the range.
        /// </summary>
        public PrimeType Count
        {
            get
            {
                PrimeType lptCount = 0;
                foreach (PrimeType liPrime in this)
                    lptCount++;
                return lptCount;
            }
        }

        /// <summary>
        /// Determines if a value in range is prime or not.
        /// </summary>
        /// <param name="test">The value to test for primality.</param>
        /// <returns>True if the value is prime, false otherwise.</returns>
        public bool this[PrimeType test]
        {
            get
            {
                if (test > mpLimit) throw new ArgumentOutOfRangeException();
                if (test <= 1) return false;
                if (test == 2) return true;
                if ((test & 1) == 0) return false;
                return !GetBitSafe(mbaOddNotPrime, test >> 1);
            }
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Gets the enumerator for the primes.
        /// </summary>
        /// <returns>The enumerator of the primes.</returns>
        public IEnumerator<PrimeType> GetEnumerator()
        {
            // Two always prime.
            yield return 2;

            // Start at first block, second MSB.
            int liBlock = 0;
            byte lbBit = 1;
            BitArrayType lbaCurrent = mbaOddNotPrime[0] >> 1;

            // For each value in range stepping in incrments of two for odd values.
            for (PrimeType lpN = 3; lpN <= mpLimit; lpN += 2)
            {
                // If current bit not set then value is prime.
                if ((lbaCurrent & 1) == 0)
                    yield return lpN;

                // Move to NSB.
                lbaCurrent >>= 1;

                // Increment bit value.
                lbBit++;

                // If block is finished.
                if (lbBit == cbBitsPerBlock) 
                {
                    // Move to first bit of next block.
                    lbBit = 0;
                    liBlock++;
                    lbaCurrent = mbaOddNotPrime[liBlock];
                }
            }
        }

        #endregion

        #region IEnumerable<PrimeType> Implementation

        /// <summary>
        /// Gets the enumerator for the primes.
        /// </summary>
        /// <returns>The enumerator for the prime numbers.</returns>
        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        #endregion
    }
}

Простые числа, найденные в 1000000000: 50 847 534 за 9 261 мс

person Aaron Murgatroyd    schedule 14.03.2012
comment
Довольно быстро, но Eratosthenes несовместим с многопоточностью, неверно; это если вы выберете правильный алгоритмический подход: разделите ваш большой массив на части, чтобы отсеять каждый сегмент, который должен быть размером с кеши процессора для повышения эффективности доступа к памяти, затем используйте количество потоков, равное количеству процессоров для обрабатывать каждую последующую страницу сегмента с одним дополнительным сегментом для обработки счетчиком / перечислителем переднего плана. Время работы вашего процессора AMD X4 следует разделить на 4, за исключением времени для подсчета / перечисления простых чисел, так что около 2,5 секунд на 1 миллиард. - person GordonBGood; 20.08.2013