C#:如何使Atkin增量筛选

时间:2009-10-14 22:39:14

标签: c# algorithm primes sieve-of-atkin

我不知道这是否可能,但我只想问。我的数学和算法技能在这里让我失望:P

问题是我现在有这个类可以产生一定数量的素数:

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])
            {
                var s = n * n;
                for (ulong k = s; k <= limit; k += s)
                    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();
    }
}

现在,我想要摆脱限制,以便序列永远不会停止(理论上)。

我认为它可能会是这样的:

  1. 从一些微不足道的限制开始
    • 找到所有素数达到极限
    • 收益所有新发现的素数
    • 增加限制(通过加倍或平方旧限制或类似的东西)
    • 转到第2步
  2. 最好的是,第二步应该只需要在旧限制和新限制之间工作。换句话说,它不应该一次又一次地找到最低素数。

    有没有办法可以做到这一点?我的主要问题是我不太明白这个算法中的xy是什么。比如,我可以使用相同的算法,但是将xy设置为oldLimit(最初为1)并将其运行到newLimit?或者如何运作?任何聪明的头脑都会有一些亮光吗?

    关键在于我不必设置该限制。因此,我可以使用Linq和Take()但我需要的许多素数,而不是担心限制是否足够高等等。

4 个答案:

答案 0 :(得分:4)

这是C#中无限制筛选的解决方案,可以使用Eratosthenes筛选(SoE)或Atkin筛选算法(SoA)实现;但是,我认为,优化的SoA解决方案的极端复杂性几乎不值得,而真正的SoE在没有太多复杂性的情况下提供相同的性能。因此,这可能只是部分答案,虽然它显示了如何实现更好的SoA算法并展示如何使用SoE实现无界序列,但它仅暗示如何组合这些以编写合理有效的SoA。

请注意,如果只想讨论这些想法的最快实现,请跳到这个答案的底部。

首先,我们应该评论这个练习的重点是生成一个无限的素数序列,以允许使用诸如Take(),TakeWhile(),Where(),Count()等等IEnumerable扩展方法作为这些方法由于增加了方法调用级别,为每次调用添加至少28个机器周期并返回枚举一个值并为每个函数添加几个级别的方法调用,因此放弃一些性能;也就是说,即使在枚举结果中使用更多命令式过滤技术以获得更好的速度,拥有(有效)无限序列仍然是有用的。

注意,在下面的简单示例中,我将范围限制为无符号32位数字(uint)的范围,尽管基本的SoE或SoA实现在效率和效率方面并不合适。一个人需要切换到一个&#34;桶&#34;用于部分筛分以提高效率的其他形式的增量筛; 然而,对于完全优化的页面分段筛,就像在最快的实施中一样,范围增加到64位范围,尽管写的可能不希望过去大约一百万亿(十到十四次幂)因为整个范围的运行时间将长达数百年。

由于问题选择SoA可能是错误的原因,首先误认为试验部(TD)主筛用于真正的SoE,然后在TD筛上使用天真的SoA,让我们建立真正有界的SoE它可以作为一种方法在几行中实现(可以根据问题的实现编码风格转换为类),如下所示:

static IEnumerable<uint> primesSoE(uint top_number) {
  if (top_number < 2u) yield break;
  yield return 2u; if (top_number < 3u) yield break;
  var BFLMT = (top_number - 3u) / 2u;
  var SQRTLMT = ((uint)(Math.Sqrt((double)top_number)) - 3u) / 2u;
  var buf = new BitArray((int)BFLMT + 1,true);
  for (var i = 0u; i <= BFLMT; ++i) if (buf[(int)i]) {
      var p = 3u + i + i; if (i <= SQRTLMT) {
        for (var j = (p * p - 3u) / 2u; j <= BFLMT; j += p)
          buf[(int)j] = false; } yield return p; } }

在i7-2700K(3.5 GHz)上计算大约16万毫秒的素数,在同一台机器上大约67秒内在32位数字范围内的203,280,221素数高达4,294,967,291(给定备用256大容量的RAM内存)。

现在,使用上面的版本与SoA进行比较是不公平的,因为真正的SoA会自动忽略2,3和5的倍数,因此引入车轮分解来为SoE做同样的事情产生以下代码:

static IEnumerable<uint> primesSoE(uint top_number) {
  if (top_number < 2u) yield break;
  yield return 2u; if (top_number < 3u) yield break;
  yield return 3u; if (top_number < 5u) yield break;
  yield return 5u; if (top_number < 7u) yield break;
  var BFLMT = (top_number - 7u) / 2u;
  var SQRTLMT = ((uint)(Math.Sqrt((double)top_number)) - 7u) / 2u;
  var buf = new BitArray((int)BFLMT + 1,true);
  byte[] WHLPTRN = { 2, 1, 2, 1, 2, 3, 1, 3 };
  for (uint i = 0u, w = 0u; i <= BFLMT; i += WHLPTRN[w], w = (w < 7u) ? ++w : 0u)
    if (buf[(int)i]) { var p = 7u + i + i; if (i <= SQRTLMT) {
        var pX2 = p + p; uint[] pa = { p, pX2, pX2 + p };
        for (uint j = (p * p - 7u) / 2u, m = w; j <= BFLMT;
                               j += pa[WHLPTRN[m] - 1u], m = (m < 7u) ? ++m : 0u)
          buf[(int)j] = false; } yield return p; } }

上述代码在大约10毫秒内计算质数为2百万,并且在与上述相同的机器上大约40秒内计算32位数的质数。

接下来,让我们确定我们可能在这里编写的SoA版本是否实际上与执行速度相同的上述代码相比具有任何优势。下面的代码遵循上面的SoE模型,并优化来自the Wikipedia article的SoA伪代码,以调整&#39; x&#39;的范围。并且&#39; y&#39;如本文所述,对于各个二次解使用单独的循环的变量,仅针对奇数解求解二次方程(和无方形抵消),组合&#34; 3 * x ^ 2&#34;二次解决同一遍中的正负二项,并消除计算上昂贵的模运算,产生的代码比在那里发布的伪代码的原始版本快3倍以上,并且在问题中使用这里。有界SoA的代码如下:

static IEnumerable<uint> primesSoA(uint top_number) {
  if (top_number < 2u) yield break;
  yield return 2u; if (top_number < 3u) yield break;
  yield return 3u; if (top_number < 5u) yield break;
  var BFLMT = (top_number - 3u) / 2u; var buf = new BitArray((int)BFLMT + 1, false);
  var SQRT = (uint)(Math.Sqrt((double)top_number)); var SQRTLMT = (SQRT - 3u) / 2u;
  for (uint x = 1u, s = 1u, mdx12 = 5u, dmdx12 = 0u; s <= BFLMT; ++x, s += ((x << 1) - 1u) << 1) {
    for (uint y = 1u, n = s, md12 = mdx12, dmd12 = 8u; n <= BFLMT; y += 2, n += (y - 1u) << 1) {
      if ((md12 == 1) || (md12 == 5)) buf[(int)n] = buf[(int)n] ^ true;
      md12 += dmd12; if (md12 >= 12) md12 -= 12; dmd12 += 8u; if (dmd12 >= 12u) dmd12 -= 12u; }
    mdx12 += dmdx12; if (mdx12 >= 12u) mdx12 -= 12u; dmdx12 += 8u; if (dmdx12 >= 12u) dmdx12 -= 12u; }
  for (uint x = 1u, s = 0u, mdx12 = 3u, dmdx12 = 8u; s <= BFLMT; ++x, s += x << 1) {
    int y = 1 - (int)x, n = (int)s, md12 = (int)mdx12, dmd12 = ((-y - 1) << 2) % 12;
    for (; (y < 0) && (uint)n <= BFLMT; y += 2, n += (-y + 1) << 1) {
      if (md12 == 11) buf[(int)n] = buf[(int)n] ^ true;
      md12 += dmd12; if (md12 >= 12) md12 -= 12; dmd12 += 4; if (dmd12 >= 12) dmd12 -= 12; }
    if (y < 1) { y = 2; n += 2; md12 += 4; dmd12 = 0; } else { n += 1; md12 += 2; dmd12 = 8; }
    if (md12 >= 12) md12 -= 12; for (; (uint)n <= BFLMT; y += 2, n += (y - 1) << 1) {
      if (md12 == 7) buf[(int)n] = buf[(int)n] ^ true;
      md12 += dmd12; if (md12 >= 12) md12 -= 12; dmd12 += 8; if (dmd12 >= 12) dmd12 -= 12; }
    mdx12 += dmdx12; if (mdx12 >= 12) mdx12 -= 12; dmdx12 += 4; if (dmdx12 >= 12) dmdx12 -= 12; }
  for (var i = 0u; i<=BFLMT; ++i) if (buf[(int)i]) { var p = 3u+i+i; if (i<=SQRTLMT) { var sqr = p*p;
        for (var j = (sqr - 3ul) / 2ul; j <= BFLMT; j += sqr) buf[(int)j] = false; } yield return p; } }

由于没有完全优化的操作次数,这仍然是车轮分解SoE算法的两倍慢。可以对SoA代码进行进一步优化,如使用模60操作和原始(非伪代码)算法,并使用位打包仅处理除3和5之外的倍数,以使代码在性能上与SoE相当接近或者甚至略微超过它,但我们越来越接近Berstein实施的复杂性来实现这种性能。我的观点是&#34;为什么SoA,当一个人努力获得与SoE相同的表现时?&#34;。

现在对于无界素数序列,最简单的方法是定义一个Uint32.MaxValue的const top_number并消除primesSoE或primesSoA方法中的参数。这有点效率低,因为无论处理的实际质数值如何,仍然在整数范围内进行剔除,这使得对小范围质数的确定花费的时间比它应该长得多。除了这个以及极端的内存使用之外,还有其他理由去分段版本的素数筛:首先,使用主要在CPU L1或L2数据缓存中的数据的算法由于更高效的内存访问而处理得更快,并且其次,因为分段允许将作业轻松拆分成可以使用多核处理器在后台剔除的页面,以便加速,这可以与使用的核心数量成比例。

为了提高效率,我们应该选择CPU L1或L2缓存大小的数组大小,对于大多数现代CPU而言至少为16 Kilobytes,以避免缓存抖动,因为我们从阵列中剔除素数组合,这意味着BitArray的大小可以是大(每字节8位)或128千比特的八倍。由于我们只需要筛选奇数素数,这代表了超过25万的数字范围,这意味着分段版本将仅使用8个段来筛选到欧拉问题10所要求的200万个。可以保存结果来自最后一段并从那一点继续,但这将阻止将此代码适应多核情况,其中一个人将剔除降级到多个线程的后台以充分利用多核处理器。 (单线程)无界SoE的C#代码如下:

static IEnumerable<uint> primesSoE() { yield return 2u; yield return 3u; yield return 5u;
  const uint L1CACHEPOW = 14u + 3u, L1CACHESZ = (1u << (int)L1CACHEPOW); //for 16K in bits...
  const uint BUFSZ = L1CACHESZ / 15u * 15u; //an even number of wheel rotations
  var buf = new BitArray((int)BUFSZ);
  const uint MAXNDX = (uint.MaxValue - 7u) / 2u; //need maximum for number range
  var SQRTNDX = ((uint)Math.Sqrt(uint.MaxValue) - 7u) / 2u;
  byte[] WHLPTRN = { 2, 1, 2, 1, 2, 3, 1, 3 }; //the 2,3,5 factorial wheel, (sum) 15 elements long
  byte[] WHLPOS = { 0, 2, 3, 5, 6, 8, 11, 12 }; //get wheel position from index
  byte[] WHLNDX = { 0, 0, 1, 2, 2, 3, 4, 4, 5, 5, 5, 6, 7, 7, 7, //get index from position
                    0, 0, 1, 2, 2, 3, 4, 4, 5, 5, 5, 6, 7 }; //allow for overflow
  byte[] WHLRNDUP = { 0, 2, 2, 3, 5, 5, 6, 8, 8, 11, 11, 11, 12, 15, //allow for overflow...
                      15, 15, 17, 17, 18, 20, 20, 21, 23, 23, 26, 26, 26, 27 };
  uint BPLMT = (ushort.MaxValue - 7u) / 2u; var bpbuf = new BitArray((int)BPLMT + 1, true);
  for (var i = 0; i <= 124; ++i) if (bpbuf[i]) { var p = 7 + i + i; //initialize baseprimes array
      for (var j = (p * p - 7) / 2; j <= BPLMT; j += p) bpbuf[j] = false; } var pa = new uint[3];
  for (uint i = 0u, w = 0, si = 0; i <= MAXNDX;
        i += WHLPTRN[w], si += WHLPTRN[w], si = (si >= BUFSZ) ? 0u : si, w = (w < 7u) ? ++w : 0u) {
    if (si == 0) { buf.SetAll(true);
      for (uint j = 0u, bw = 0u; j <= BPLMT; j += WHLPTRN[bw], bw = (bw < 7u) ? ++bw : 0u)
        if (bpbuf[(int)j]) { var p = 7u+j+j; var pX2=p+p; var k = p * (j + 3u) + j;
          if (k >= i + BUFSZ) break; pa[0] = p; pa[1] = pX2; pa[2] = pX2 + p; var sw = bw; if (k < i) {
            k = (i - k) % (15u * p); if (k != 0) { var os = WHLPOS[bw]; sw = os + ((k + p - 1u) / p);
              sw = WHLRNDUP[sw]; k = (sw - os) * p - k; sw = WHLNDX[sw]; } } else k -= i;
          for (; k<BUFSZ; k+=pa[WHLPTRN[sw]-1], sw=(sw<7u) ? ++sw : 0u) buf[(int)k]=false; } }
    if (buf[(int)si]) yield return 7u + i + i; } }

上面的代码大约需要16毫秒来筛选质数高达200万次,大约30秒才能筛选出完整的32位数字范围。这个代码比使用阶梯轮的类似版本更快,而不需要对大数范围进行分段,因为即使代码在计算上更复杂,也可以节省大量时间而不会破坏CPU缓存。大部分的复杂性在于每个段的每个基本质数的新起始索引的计算,这可以通过保存每个段的每个质数的状态来减少,但是上述代码可以容易地被转换以便运行剔除过程。对于具有四个内核的机器,将单独的后台线程进一步加速四倍,对于具有八个内核的机器,甚至更多。不使用BitArray类并通过位掩码寻址各个位位置将提供大约两倍的进一步加速,并且因子轮的进一步增加将提供大约另外的时间减少到大约三分之二。对于通过车轮分解消除的值,消除的索引中的比特阵列的更好的打包将略微提高大范围的效率,但是也会稍微增加比特操纵的复杂性。然而,所有这些SoE优化都不会接近Berstein SoA实现的极端复杂性,但会以大致相同的速度运行。

要将上述代码从SoE转换为SoA,我们只需要从有界情况更改为SoA剔除代码,但需要修改为每个页面段重新计算起始地址,例如计算起始地址的起始地址在SoE的情况下,但是在操作中甚至更复杂,因为正方形推进了数字的平方而不是简单的素数倍数。我会给学生留下必要的修改,虽然我没有真正看到这一点,因为合理优化的SoA并不比当前版本的SoE快,而且要复杂得多;)

<强> EDIT_ADD:

注意:下面的代码已被更正,因为原始发布的代码对于提供的素数序列是正确的,它比它需要的慢一半,因为它剔除了平方根以下的所有数字范围而不是只有找到的基本素数直到范围的平方根。

最有效和最简单的优化是将每个段页面的剔除操作降级为后台线程,这样,给定足够的CPU内核,枚举素数的主要限制是枚举循环本身,在这种情况下,32中的所有素数在没有其他优化的情况下,上述参考机器(在C#中)可以在大约十秒钟内枚举位数范围,所有其他优化包括编写IEnumerable接口实现而不是使用内置迭代器将其减少到大约六所有203,280,221素数在32位数字范围内的秒数(1.65秒到10亿),同样大部分时间只是枚举结果。下面的代码包含许多修改,包括使用Task使用的Dotnet Framework 4 ThreadPool中的后台任务,使用作为查找表实现的状态机来实现进一步的位打包,使得素数枚举更快,并重新编写算法作为一个使用&#34;滚动你自己&#34;的可枚举类。提高效率的迭代器:

class fastprimesSoE : IEnumerable<uint>, IEnumerable {
  struct procspc { public Task tsk; public uint[] buf; }
  struct wst { public byte msk; public byte mlt; public byte xtr; public byte nxt; }
  static readonly uint NUMPROCS = (uint)Environment.ProcessorCount + 1u; const uint CHNKSZ = 1u;
  const uint L1CACHEPOW = 14u, L1CACHESZ = (1u << (int)L1CACHEPOW), PGSZ = L1CACHESZ >> 2; //for 16K in bytes...
  const uint BUFSZ = CHNKSZ * PGSZ; //number of uints even number of caches in chunk
  const uint BUFSZBTS = 15u * BUFSZ << 2; //even in wheel rotations and uints (and chunks)
  static readonly byte[] WHLPTRN = { 2, 1, 2, 1, 2, 3, 1, 3 }; //the 2,3,5 factorial wheel, (sum) 15 elements long
  static readonly byte[] WHLPOS = { 0, 2, 3, 5, 6, 8, 11, 12 }; //get wheel position from index
  static readonly byte[] WHLNDX = { 0, 1, 1, 2, 3, 3, 4, 5, 5, 6, 6, 6, 7, 0, 0, 0 }; //get index from position
  static readonly byte[] WHLRNDUP = { 0, 2, 2, 3, 5, 5, 6, 8, 8, 11, 11, 11, 12, 15, 15, 15, //allow for overflow...
                                      17, 17, 18, 20, 20, 21, 23, 23, 26, 26, 26, 27, 30, 30, 30 }; //round multiplier up
  const uint BPLMT = (ushort.MaxValue - 7u) / 2u; const uint BPSZ = BPLMT / 60u + 1u;
  static readonly uint[] bpbuf = new uint[BPSZ]; static readonly wst[] WHLST = new wst[64];
  static void cullpg(uint i, uint[] b, int strt, int cnt) { Array.Clear(b, strt, cnt);
    for (uint j = 0u, wp = 0, bw = 0x31321212u, bi = 0u, v = 0xc0881000u, bm = 1u; j <= BPLMT;
      j += bw & 0xF, wp = wp < 12 ? wp += bw & 0xF : 0, bw = (bw > 3u) ? bw >>= 4 : 0x31321212u) {
      var p = 7u + j + j; var k = p * (j + 3u) + j; if (k >= (i + (uint)cnt * 60u)) break;
      if ((v & bm) == 0u) { if (k < i) { k = (i - k) % (15u * p); if (k != 0) {
            var sw = wp + ((k + p - 1u) / p); sw = WHLRNDUP[sw]; k = (sw - wp) * p - k; } }
        else k -= i; var pd = p / 15;
        for (uint l = k / 15u + (uint)strt * 4u, lw = ((uint)WHLNDX[wp] << 3) + WHLNDX[k % 15u];
               l < (uint)(strt + cnt) * 4u; ) { var st = WHLST[lw];
          b[l >> 2] |= (uint)st.msk << (int)((l & 3) << 3); l += st.mlt * pd + st.xtr; lw = st.nxt; } }
      if ((bm <<= 1) == 0u) { v = bpbuf[++bi]; bm = 1u; } } }
  static fastprimesSoE() {
    for (var x = 0; x < 8; ++x) { var p = 7 + 2 * WHLPOS[x]; var pr = p % 15;
      for (int y = 0, i = ((p * p - 7) / 2); y < 8; ++y) { var m = WHLPTRN[(x + y) % 8]; i %= 15;
        var n = WHLNDX[i]; i += m * pr; WHLST[x * 8 + n] = new wst { msk = (byte)(1 << n), mlt = m,
                                                                     xtr = (byte)(i / 15),
                                                                     nxt = (byte)(8 * x + WHLNDX[i % 15]) }; }
    } cullpg(0u, bpbuf, 0, bpbuf.Length);  } //init baseprimes
  class nmrtr : IEnumerator<uint>, IEnumerator, IDisposable {
    procspc[] ps = new procspc[NUMPROCS]; uint[] buf;
    Task dlycullpg(uint i, uint[] buf) {  return Task.Factory.StartNew(() => {
        for (var c = 0u; c < CHNKSZ; ++c) cullpg(i + c * PGSZ * 60, buf, (int)(c * PGSZ), (int)PGSZ); }); }
    public nmrtr() {
      for (var i = 0u; i < NUMPROCS; ++i) ps[i] = new procspc { buf = new uint[BUFSZ] };
      for (var i = 1u; i < NUMPROCS; ++i) { ps[i].tsk = dlycullpg((i - 1u) * BUFSZBTS, ps[i].buf); } buf = ps[0].buf;  }
    public uint Current { get { return this._curr; } } object IEnumerator.Current { get { return this._curr; } }
    uint _curr; int b = -4; uint i = 0, w = 0; uint v, msk = 0;
    public bool MoveNext() {
      if (b < 0) if (b == -1) { _curr = 7; b += (int)BUFSZ; }
        else { if (b++ == -4) this._curr = 2u; else this._curr = 7u + ((uint)b << 1); return true; }
      do {  i += w & 0xF; if ((w >>= 4) == 0) w = 0x31321212u; if ((this.msk <<= 1) == 0) {
          if (++b >= BUFSZ) { b = 0; for (var prc = 0; prc < NUMPROCS - 1; ++prc) ps[prc] = ps[prc + 1];
            ps[NUMPROCS - 1u].buf = buf; var low = i + (NUMPROCS - 1u) * BUFSZBTS;
            ps[NUMPROCS - 1u].tsk = dlycullpg(i + (NUMPROCS - 1u) * BUFSZBTS, buf);
            ps[0].tsk.Wait(); buf = ps[0].buf; } v = buf[b]; this.msk = 1; } }
      while ((v & msk) != 0u); if (_curr > (_curr = 7u + i + i)) return false; else return true;  }
    public void Reset() { throw new Exception("Primes enumeration reset not implemented!!!"); }
    public void Dispose() { }
  }
  public IEnumerator<uint> GetEnumerator() { return new nmrtr(); }
  IEnumerator IEnumerable.GetEnumerator() { return new nmrtr(); } }

请注意,由于设置和初始化数组的开销,此代码不会比上一版本的小范围素数快,因为高达一百或两百万,但对于大到四的较大范围则相当快十亿多。它比Atkin筛的问题实施速度快约8倍,但对于较大范围的速度,速度可提高20至50倍,最高可达40亿以上。在代码中有定义的常量设置基本高速缓存大小以及可以略微调整性能的每个线程(CHNKSZ)的剔除数量。可以尝试一些轻微的进一步优化,可以将大质数的执行时间减少两倍,例如2,3,5,7轮的进一步填充,而不仅仅是2,3,5轮。包装减少约25%(可能将执行时间减少到三分之二),并通过车轮因子预先剔除页面段,直至17倍,进一步减少约20%,但这些大约在20%的范围内与可以利用独特本机代码优化的C代码相比,纯C#代码可以做什么。

无论如何,如果打算使用IEnumberable接口进行输出,那么很难进一步优化,因为问题需要大约三分之二的时间用于枚举找到的素数而且只有大约三分之一的时间是花在剔除复合数字上。更好的方法是在类上编写方法来实现所需的结果,如CountTo,ElementAt,SumTo等,以避免完全枚举。

但我确实做了additional answer的额外优化,尽管存在额外的复杂性,但显示出额外的好处,并且进一步说明了为什么人们不想使用SoA因为完全优化SoE实际上更好。

答案 1 :(得分:3)

以下代码执行my previous answer底部讨论的优化,并包含以下功能:

  1. 可用范围已增加到64但无符号数 范围为18,446,744,073,709,551,615,范围溢出检查 删除,因为不太可能想要运行该程序 数百年来,它将需要处理全部范围 数字到那个极限。这在处理上的成本非常低 时间,因为可以使用32位页面范围完成分页,只有 最终的主要输出需要计算为64位数。
  2. 它将车轮分解从2,3,5车轮增加到使用a 2,3,5,7素数因子轮具有额外的预剔除复合材料 使用11,13和17的附加素数的数字 大大减少了冗余复合数剔除(现在只有 剔除每个复合数平均约1.5倍)。应有 这个(DotNet相关的)计算开销(也是 适用于2,3,5轮的前一版本)实际时间 节省剔除并不是很好,但列举了答案 由于许多&#34;简单&#34;综合数字 在打包位表示中跳过。
  3. 它仍然使用DotNet 4及更高版本的任务并行库(TPL) 用于基于每页的线程池进行多线程处理。
  4. 它现在使用自动支持的基本素数表示 将此类包含的数组增长为更多基本素数 需要作为线程安全的方法而不是固定的预先计算 以前使用的基本素数组。
  5. 基本素数表示已减少到每个基数一个字节 进一步减少内存占用的主要原因;因此,总数 除代码之外的内存占用是保存此基础的数组 素数的素数表示直到平方根 正在处理的当前范围,以及打包的位页缓冲区 目前设置的L2缓存大小为256千字节 (最小页面大小为14,586字节乘以17的CHNKSZ 每个CPU核心加一个额外的前台缓冲区 要处理的任务。使用此代码,大约有三兆字节 足以处理高达十到十四的主要范围 功率。由于允许有效的多处理,速度以及速度, 这减少内存需求是使用a的另一个优点 分页筛选实施。

    class UltimatePrimesSoE : IEnumerable<ulong> {
      static readonly uint NUMPRCSPCS = (uint)Environment.ProcessorCount + 1; const uint CHNKSZ = 17;
      const int L1CACHEPOW = 14, L1CACHESZ = (1 << L1CACHEPOW), MXPGSZ = L1CACHESZ / 2; //for buffer ushort[]
      //the 2,3,57 factorial wheel increment pattern, (sum) 48 elements long, starting at prime 19 position
      static readonly byte[] WHLPTRN = { 2,3,1,3,2,1,2,3,3,1,3,2,1,3,2,3,4,2,1,2,1,2,4,3,
                                                                               2,3,1,2,3,1,3,3,2,1,2,3,1,3,2,1,2,1,5,1,5,1,2,1 }; const uint FSTCP = 11;
      static readonly byte[] WHLPOS; static readonly byte[] WHLNDX; //to look up wheel indices from position index
      static readonly byte[] WHLRNDUP; //to look up wheel rounded up index positon values, allow for overfolw
      static readonly uint WCRC = WHLPTRN.Aggregate(0u, (acc, n) => acc + n);
      static readonly uint WHTS = (uint)WHLPTRN.Length; static readonly uint WPC = WHTS >> 4;
      static readonly byte[] BWHLPRMS = { 2, 3, 5, 7, 11, 13, 17 }; const uint FSTBP = 19;
      static readonly uint BWHLWRDS = BWHLPRMS.Aggregate(1u, (acc, p) => acc * p) / 2 / WCRC * WHTS / 16;
      static readonly uint PGSZ = MXPGSZ / BWHLWRDS * BWHLWRDS; static readonly uint PGRNG = PGSZ * 16 / WHTS * WCRC;
      static readonly uint BFSZ = CHNKSZ * PGSZ, BFRNG = CHNKSZ * PGRNG; //number of uints even number of caches in chunk
      static readonly ushort[] MCPY; //a Master Copy page used to hold the lower base primes preculled version of the page
      struct Wst { public ushort msk; public byte mlt; public byte xtr; public ushort nxt; }
      static readonly byte[] PRLUT; /*Wheel Index Look Up Table */ static readonly Wst[] WSLUT; //Wheel State Look Up Table
      static readonly byte[] CLUT; // a Counting Look Up Table for very fast counting of primes
      static int count(uint bitlim, ushort[] buf) { //very fast counting
        if (bitlim < BFRNG) { var addr = (bitlim - 1) / WCRC; var bit = WHLNDX[bitlim - addr * WCRC] - 1; addr *= WPC;
          for (var i = 0; i < 3; ++i) buf[addr++] |= (ushort)((unchecked((ulong)-2) << bit) >> (i << 4)); }
        var acc = 0; for (uint i = 0, w = 0; i < bitlim; i += WCRC)
          acc += CLUT[buf[w++]] + CLUT[buf[w++]] + CLUT[buf[w++]]; return acc; }
      static void cull(ulong lwi, ushort[] b) { ulong nlwi = lwi;
        for (var i = 0u; i < b.Length; nlwi += PGRNG, i += PGSZ) MCPY.CopyTo(b, i); //copy preculled lower base primes.
        for (uint i = 0, pd = 0; ; ++i) { pd += (uint)baseprms[i] >> 6;
          var wi = baseprms[i] & 0x3Fu; var wp = (uint)WHLPOS[wi]; var p = pd * WCRC + PRLUT[wi];
          var pp = (p - FSTBP) >> 1; var k = (ulong)p * (pp + ((FSTBP - 1) >> 1)) + pp;
          if (k >= nlwi) break; if (k < lwi) { k = (lwi - k) % (WCRC * p);
            if (k != 0) { var nwp = wp + (uint)((k + p - 1) / p); k = (WHLRNDUP[nwp] - wp) * p - k;
              if (nwp >= WCRC) wp = 0; else wp = nwp; } }
          else k -= lwi; var kd = k / WCRC; var kn = WHLNDX[k - kd * WCRC];
          for (uint wrd = (uint)kd * WPC + (uint)(kn >> 4), ndx = wi * WHTS + kn; wrd < b.Length; ) {
            var st = WSLUT[ndx]; b[wrd] |= st.msk; wrd += st.mlt * pd + st.xtr; ndx = st.nxt; } } }
      static Task cullbf(ulong lwi, ushort[] b, Action<ushort[]> f) {
        return Task.Factory.StartNew(() => { cull(lwi, b); f(b); }); }
      class Bpa {   //very efficient auto-resizing thread-safe read-only indexer class to hold the base primes array
        byte[] sa = new byte[0]; uint lwi = 0, lpd = 0; object lck = new object();
        public uint this[uint i] { get { if (i >= this.sa.Length) lock (this.lck) {
                var lngth = this.sa.Length; while (i >= lngth) {
                  var bf = (ushort[])MCPY.Clone(); if (lngth == 0) {
                    for (uint bi = 0, wi = 0, w = 0, msk = 0x8000, v = 0; w < bf.Length;
                        bi += WHLPTRN[wi++], wi = (wi >= WHTS) ? 0 : wi) {
                      if (msk >= 0x8000) { msk = 1; v = bf[w++]; } else msk <<= 1;
                      if ((v & msk) == 0) { var p = FSTBP + (bi + bi); var k = (p * p - FSTBP) >> 1;
                        if (k >= PGRNG) break; var pd = p / WCRC; var kd = k / WCRC; var kn = WHLNDX[k - kd * WCRC];
                        for (uint wrd = kd * WPC + (uint)(kn >> 4), ndx = wi * WHTS + kn; wrd < bf.Length; ) {
                          var st = WSLUT[ndx]; bf[wrd] |= st.msk; wrd += st.mlt * pd + st.xtr; ndx = st.nxt; } } } }
                  else { this.lwi += PGRNG; cull(this.lwi, bf); }
                  var c = count(PGRNG, bf); var na = new byte[lngth + c]; sa.CopyTo(na, 0);
                  for (uint p = FSTBP + (this.lwi << 1), wi = 0, w = 0, msk = 0x8000, v = 0;
                      lngth < na.Length; p += (uint)(WHLPTRN[wi++] << 1), wi = (wi >= WHTS) ? 0 : wi) {
                    if (msk >= 0x8000) { msk = 1; v = bf[w++]; } else msk <<= 1; if ((v & msk) == 0) {
                      var pd = p / WCRC; na[lngth++] = (byte)(((pd - this.lpd) << 6) + wi); this.lpd = pd; }
                  } this.sa = na; } } return this.sa[i]; } } }
      static readonly Bpa baseprms = new Bpa();
      static UltimatePrimesSoE() {
        WHLPOS = new byte[WHLPTRN.Length + 1]; //to look up wheel position index from wheel index
        for (byte i = 0, acc = 0; i < WHLPTRN.Length; ++i) { acc += WHLPTRN[i]; WHLPOS[i + 1] = acc; }
        WHLNDX = new byte[WCRC + 1]; for (byte i = 1; i < WHLPOS.Length; ++i) {
          for (byte j = (byte)(WHLPOS[i - 1] + 1); j <= WHLPOS[i]; ++j) WHLNDX[j] = i; }
        WHLRNDUP = new byte[WCRC * 2]; for (byte i = 1; i < WHLRNDUP.Length; ++i) {
          if (i > WCRC) WHLRNDUP[i] = (byte)(WCRC + WHLPOS[WHLNDX[i - WCRC]]); else WHLRNDUP[i] = WHLPOS[WHLNDX[i]]; }
        Func<ushort, int> nmbts = (v) => { var acc = 0; while (v != 0) { acc += (int)v & 1; v >>= 1; } return acc; };
        CLUT = new byte[1 << 16]; for (var i = 0; i < CLUT.Length; ++i) CLUT[i] = (byte)nmbts((ushort)(i ^ -1));
        PRLUT = new byte[WHTS]; for (var i = 0; i < PRLUT.Length; ++i) {
          var t = (uint)(WHLPOS[i] * 2) + FSTBP; if (t >= WCRC) t -= WCRC; if (t >= WCRC) t -= WCRC; PRLUT[i] = (byte)t; }
        WSLUT = new Wst[WHTS * WHTS]; for (var x = 0u; x < WHTS; ++x) {
          var p = FSTBP + 2u * WHLPOS[x]; var pr = p % WCRC;
          for (uint y = 0, pos = (p * p - FSTBP) / 2; y < WHTS; ++y) {
            var m = WHLPTRN[(x + y) % WHTS];
            pos %= WCRC; var posn = WHLNDX[pos]; pos += m * pr; var nposd = pos / WCRC; var nposn = WHLNDX[pos - nposd * WCRC];
            WSLUT[x * WHTS + posn] = new Wst { msk = (ushort)(1 << (int)(posn & 0xF)), mlt = (byte)(m * WPC),
                                               xtr = (byte)(WPC * nposd + (nposn >> 4) - (posn >> 4)),
                                               nxt = (ushort)(WHTS * x + nposn) }; } }
        MCPY = new ushort[PGSZ]; foreach (var lp in BWHLPRMS.SkipWhile(p => p < FSTCP)) { var p = (uint)lp;
          var k = (p * p - FSTBP) >> 1; var pd = p / WCRC; var kd = k / WCRC; var kn = WHLNDX[k - kd * WCRC];
          for (uint w = kd * WPC + (uint)(kn >> 4), ndx = WHLNDX[(2 * WCRC + p - FSTBP) / 2] * WHTS + kn; w < MCPY.Length; ) {
            var st = WSLUT[ndx]; MCPY[w] |= st.msk; w += st.mlt * pd + st.xtr; ndx = st.nxt; } } }
      struct PrcsSpc { public Task tsk; public ushort[] buf; }
      class nmrtr : IEnumerator<ulong>, IEnumerator, IDisposable {
        PrcsSpc[] ps = new PrcsSpc[NUMPRCSPCS]; ushort[] buf;
        public nmrtr() { for (var s = 0u; s < NUMPRCSPCS; ++s) ps[s] = new PrcsSpc { buf = new ushort[BFSZ] };
          for (var s = 1u; s < NUMPRCSPCS; ++s) {
            ps[s].tsk = cullbf((s - 1u) * BFRNG, ps[s].buf, (bfr) => { }); } buf = ps[0].buf; }
        ulong _curr, i = (ulong)-WHLPTRN[WHTS - 1]; int b = -BWHLPRMS.Length - 1; uint wi = WHTS - 1; ushort v, msk = 0;
        public ulong Current { get { return this._curr; } } object IEnumerator.Current { get { return this._curr; } }
        public bool MoveNext() {
          if (b < 0) { if (b == -1) b += buf.Length; //no yield!!! so automatically comes around again
            else { this._curr = (ulong)BWHLPRMS[BWHLPRMS.Length + (++b)]; return true; } }
          do {
            i += WHLPTRN[wi++]; if (wi >= WHTS) wi = 0; if ((this.msk <<= 1) == 0) {
              if (++b >= BFSZ) { b = 0; for (var prc = 0; prc < NUMPRCSPCS - 1; ++prc) ps[prc] = ps[prc + 1];
                ps[NUMPRCSPCS - 1u].buf = buf;
                ps[NUMPRCSPCS - 1u].tsk = cullbf(i + (NUMPRCSPCS - 1u) * BFRNG, buf, (bfr) => { });
                ps[0].tsk.Wait(); buf = ps[0].buf; } v = buf[b]; this.msk = 1; } }
          while ((v & msk) != 0u); _curr = FSTBP + i + i; return true; }
        public void Reset() { throw new Exception("Primes enumeration reset not implemented!!!"); }
        public void Dispose() { } }
      public IEnumerator<ulong> GetEnumerator() { return new nmrtr(); }
      IEnumerator IEnumerable.GetEnumerator() { return new nmrtr(); }
      static void IterateTo(ulong top_number, Action<ulong, uint, ushort[]> actn) {
        PrcsSpc[] ps = new PrcsSpc[NUMPRCSPCS]; for (var s = 0u; s < NUMPRCSPCS; ++s) ps[s] = new PrcsSpc {
          buf = new ushort[BFSZ], tsk = Task.Factory.StartNew(() => { }) };
        var topndx = (top_number - FSTBP) >> 1; for (ulong ndx = 0; ndx <= topndx; ) {
          ps[0].tsk.Wait(); var buf = ps[0].buf; for (var s = 0u; s < NUMPRCSPCS - 1; ++s) ps[s] = ps[s + 1];
          var lowi = ndx; var nxtndx = ndx + BFRNG; var lim = topndx < nxtndx ? (uint)(topndx - ndx + 1) : BFRNG;
          ps[NUMPRCSPCS - 1] = new PrcsSpc { buf = buf, tsk = cullbf(ndx, buf, (b) => actn(lowi, lim, b)) };
          ndx = nxtndx; } for (var s = 0u; s < NUMPRCSPCS; ++s) ps[s].tsk.Wait(); }
      public static long CountTo(ulong top_number) {
        if (top_number < FSTBP) return BWHLPRMS.TakeWhile(p => p <= top_number).Count();
        var cnt = (long)BWHLPRMS.Length;
        IterateTo(top_number, (lowi, lim, b) => { Interlocked.Add(ref cnt, count(lim, b)); }); return cnt; }
      public static ulong SumTo(uint top_number) {
        if (top_number < FSTBP) return (ulong)BWHLPRMS.TakeWhile(p => p <= top_number).Aggregate(0u, (acc, p) => acc += p);
        var sum = (long)BWHLPRMS.Aggregate(0u, (acc, p) => acc += p);
        Func<ulong, uint, ushort[], long> sumbf = (lowi, bitlim, buf) => {
          var acc = 0L; for (uint i = 0, wi = 0, msk = 0x8000, w = 0, v = 0; i < bitlim;
              i += WHLPTRN[wi++], wi = wi >= WHTS ? 0 : wi) {
            if (msk >= 0x8000) { msk = 1; v = buf[w++]; } else msk <<= 1;
            if ((v & msk) == 0) acc += (long)(FSTBP + ((lowi + i) << 1)); } return acc; };
        IterateTo(top_number, (pos, lim, b) => { Interlocked.Add(ref sum, sumbf(pos, lim, b)); }); return (ulong)sum; }
      static void IterateUntil(Func<ulong, ushort[], bool> prdct) {
        PrcsSpc[] ps = new PrcsSpc[NUMPRCSPCS];
        for (var s = 0u; s < NUMPRCSPCS; ++s) { var buf = new ushort[BFSZ];
          ps[s] = new PrcsSpc { buf = buf, tsk = cullbf(s * BFRNG, buf, (bfr) => { }) }; }
        for (var ndx = 0UL; ; ndx += BFRNG) {
          ps[0].tsk.Wait(); var buf = ps[0].buf; var lowi = ndx; if (prdct(lowi, buf)) break;
          for (var s = 0u; s < NUMPRCSPCS - 1; ++s) ps[s] = ps[s + 1];
          ps[NUMPRCSPCS - 1] = new PrcsSpc { buf = buf,
                                             tsk = cullbf(ndx + NUMPRCSPCS * BFRNG, buf, (bfr) => { }) }; } }
      public static ulong ElementAt(long n) {
        if (n < BWHLPRMS.Length) return (ulong)BWHLPRMS.ElementAt((int)n);
        long cnt = BWHLPRMS.Length; var ndx = 0UL; var cycl = 0u; var bit = 0u; IterateUntil((lwi, bfr) => {
          var c = count(BFRNG, bfr); if ((cnt += c) < n) return false; ndx = lwi; cnt -= c; c = 0;
          do { var w = cycl++ * WPC; c = CLUT[bfr[w++]] + CLUT[bfr[w++]] + CLUT[bfr[w]]; cnt += c; } while (cnt < n);
          cnt -= c; var y = (--cycl) * WPC; ulong v = ((ulong)bfr[y + 2] << 32) + ((ulong)bfr[y + 1] << 16) + bfr[y];
          do { if ((v & (1UL << ((int)bit++))) == 0) ++cnt; } while (cnt <= n); --bit; return true;
        }); return FSTBP + ((ndx + cycl * WCRC + WHLPOS[bit]) << 1); } }
    
  6. 上面的代码大约需要59毫秒才能找到200万的素数(由于初始化开销,比其他一些更简单的代码略慢),但计算的素数为10亿,全数范围在1.55和5.95秒, 分别。这并不比上一版本快得多,因为在查找的素数的枚举中额外的数组绑定检查的DotNet额外开销与剔除复合数字所花费的时间相比,这少于计算所花时间的三分之一因此,在枚举中额外的时间(由于每个主要候选者的额外数组边界检查)抵消了剔除复合物的节省。但是,对于涉及素数的许多任务,人们不需要枚举所有素数,而只需计算答案而不需要枚举。

    由于上述原因,本课程提供了示例静态方法&#34; CountTo&#34;,&#34; SumTo&#34;和&#34; ElementAt&#34;将素数计数或求和到给定的上限或分别输出从零开始的第n个素数。 &#34; CountTo&#34; 方法将在 0.32和1.29秒内产生到10亿和32位数字范围的质数数量分别; &#34; ElementAt&#34; 方法将分别在 0.32和1.25秒中生成这些范围中的最后一个元素,并且&#34; SumTo&#34; 方法分别在 0.49和1.98秒中生成这些范围内所有素数的总和。这个程序计算所有素数的总和为40亿加,因为这里的时间比许多天真的实现可以将所有素数加到200万,如欧拉问题10,超过实际范围的2000倍!

    此代码仅比primesieve使用的高度优化的C代码慢大约四倍,其速度较慢的原因主要是由于DotNet,如下所示(讨论256千字节缓冲区的情况,是L2缓存的大小):

    1. 大部分执行时间都花在主复合剔除上 循环,这是循环的最后一个&#34;&#34;在私人静态&#34;淘汰&#34; 方法&#34;并且每个循环只包含四个语句加上范围 检查。
    2. 在DotNet中,这个编译需要大约21.83个CPU时钟周期 循环,包括两个数组边界约5个时钟周期 检查每个循环。
    3. 非常高效的C编译器将此循环转换为仅约 8.33个时钟周期,优势约为2.67倍。
    4. Primesieve还使用极端手动&#34;循环展开&#34;优化到 将每个循环执行工作的平均时间减少到大约4.17 每个复合剔除循环的时钟周期,额外增益为2 时间和总收益约为5.3倍。
    5. 现在,高度优化的C代码都不像Hyper Thread(HT)那样 以及效率较低的Just In Time(JIT)编译器生成的代码 而且,primesieve使用的OpemMP多线程也没有 看起来像使用Thread一样适应这个问题 池线程在这里,所以最终的多线程增益只有大约 四次。
    6. 有人可能会考虑使用&#34;不安全&#34;消除指针 数组边界检查,并尝试过,但JIT编译器没有 优化指针以及基于正常数组的代码,因此获得了 没有数组绑定检查被效率较低的取消 代码(每个指针访问(重新)从内存加载指针地址 而不是使用已经指向该地址的寄存器 优化的数组案例)。
    7. 使用较小的缓冲区大小时,Primesieve的速度更快 可用L1缓存的大小(多线程时为16千字节) 对于i3 / i5 / i7 CPU来说,因为它更有效的代码有更多的 有利的是将平均内存访问时间减少到一个时钟周期 从大约四个时钟周期开始,这种优势远远少于a 与DotNet代码的区别,后者从更少的收益中获得更多 每减少页数处理。因此,primesieve是关于 每次使用最有效的缓冲区大小时,速度提高五倍。
    8. 这个DotNet代码将计数(CountTo)素数的数量减少到十分之一(十万亿),大约一个半小时(测试)和素数到百万亿(十到十四)在半天(估计)中,分别为20分钟和4小时以下。这在历史上是有趣的,直到1985年才知道从10到13的范围内的素数计数,因为那天(昂贵的)超级计算机需要花费太多的执行时间才能找到十倍大的范围;现在,我们可以在普通台式计算机上轻松计算这些范围内的素数(在本例中为Intel i7-2700K - 3.5 GHz)!

      使用这段代码,很容易理解为什么阿特金教授和伯恩斯坦认为SoA比SoE更快 - 一直存在至今的神话,其理由如下:

      1. 很容易让任何SoA实现计算状态数 切换和方形自由复合数字剔除(最后可以 使用与固有使用相同的2,3,5轮优化进行优化 通过SoA算法)来确定两者的总数 对于32位数字范围,这些操作大约为14亿。
      2. 伯恩斯坦&#34;等同于&#34; SoE实施到他的SoA 实现(两者都没有像这段代码那样优化), 它使用与SoA相同的2,3,5轮优化,将具有 使用相同的剔除循环总计大约18.2亿次剔除操作 计算复杂性。
      3. 因此,伯恩斯坦的成绩约为30% 与相比,他对SoE的实施是正确的 基于等效操作的数量。但是,他的 SoE的实施没有采用车轮分解和#34; MAX&#34;因为SoA对车轮的进一步程度没有太大反应 分解为2,3,5轮在&#34;烘烤&#34;基本的 算法
      4. 此处使用的车轮分解优化减少了数量 对于32位数字,复合剔除操作大约为12亿 范围;因此,这种算法使用这种程度的轮子 分解将比同等版本快16.7% 因为剔除循环可以实现大约相同的 每种算法。
      5. 具有此级别优化的SoE比写入更容易 相当于SoA,因为只需要一个状态表查找数组 剔除基本素数而不是额外的状态外观 对于四个二次方程解决方案案例中的每一个,数组都为up 产生有效的状态切换。
      6. 使用等效实现编写此代码时,代码 将针对SoA等效地响应针对SoA的C编译器优化 在primesieve中使用的SoE。
      7. SoA实施也将响应极端手动&#34;循环 展开&#34; primesieve使用的优化同样有效 对于SoE的实施。
      8. 因此,如果我完成所有工作来实施SoA 算法使用上述SoE代码的技术, 结果 SoA只会在输出时稍微减慢一点 这些素数被列举,但使用时会减慢约16.7% 静态直接方法
      9. 两种算法的内存占用量没有差别 需要表示基本素数和相同数量的 段页面缓冲区。
      10. EDIT_ADD:有趣的是,此代码在x86 32位模式下运行速度比在x64 64位模式下快30%,这可能是因为避免了将uint32数字扩展到ulong&#的轻微额外开销39; S。以上所有时序均适用于64位模式。的 END_EDIT_ADD

        总结:Atkin的分段分段实际上比最大优化的分段分段筛选了Eratosthenes的Sieve,但没有节省内存需求!!!

        我再说一遍:&#34;为什么要使用Atkin的Sieve?&#34;

答案 2 :(得分:2)

这是对提出的问题的更明确的答案,如下:

  

有没有办法(Atkin的增量Sieve-GBG)可以完成?

当然,Atkin的Sieve(SoA)可以作为分段算法实现,实际上根本不需要使用分段数组,而只需使用原始序列作为I have done functionally using F#,虽然这比使用可变数组进行剔除所允许的额外效率要慢得多。

  

我在想它可能是这样的:    1.从一些微不足道的限制开始    2.找到所有素数达到极限    3.找到所有素数达到极限    4.产生所有新发现的素数    5.增加限制(通过加倍或平方旧限制或类似的东西)    6.转到第2步

上述算法可以至少以两种不同的方式实现:1)当序列“流失”当前段并再次启动时,为'x'的每个值保存'x'和'y'的状态使用下一个段的值,或2)计算用于新段的“x”和“y”的适用对值。虽然第一种方法比较简单,但我推荐第二种方法有两个原因:1)它不使用内存来保存必须保存的x和y的所有(很多)值,只需要保存基本素数的表示在内存中进行“无方块”剔除步骤,以及2)它打开了使用多线程并为每个页面段分配独立线程操作的大门,以便在多处理器计算机上节省大量时间。

事实上,更好地理解'x'和'y'是必要的:

  

我的主要问题是我不太明白这个算法中的x和y是什么。比如,我可以使用相同的算法,但是将x和y设置为oldLimit(最初为1)并将其运行到newLimit?

有一个答案可以解决这个问题,但也许还不够明确。将这些二次方程视为潜在的无限序列序列可能更容易,其中'x'或'y'之一从其最低值开始固定而另一个变量产生每个序列的所有元素。例如,人们可以将二次方程“4 * x ^ 2 + y ^ 2”的奇数表达式视为以5,17,37,65 ......开始的序列序列,并且每个序列都具有元素如{5,13,​​29,53,...},{17,25,41,65,...},{37,45,61,85,...},{65,73, 89,125,...},......显然,这些元素中的一些不是素数,因为它们是3或5的复合物,这就是为什么这些元素需要通过模数测试消除,或者像伯恩斯坦那样消除实现,可以通过识别生成器的模运算中的模式来自动跳过这些模式,这样它们实际上甚至不会出现。

实现生成SoA的分段版本的第一种更简单的方法然后只需要保存每个序列序列的状态,这基本上是在F#增量实现中完成的(尽管使用折叠树结构来提高效率) ),可以很容易地适应在阵列页面范围内工作。对于在每个段页面的开头计算序列状态的情况,只需要计算空间中适合的元素数量,直到每个“活动”序列的新段页面中最低元素所代表的数量,其中“active”表示一个序列,其起始元素低于段页面起始索引所代表的数字。

关于如何实现基于阵列的SoA筛分段的伪代码,我已经为a related post编写了一些内容,说明了如何实现这一点。

  

关键在于我不必设置该限制。因此,我可以使用Linq,只需使用Take(),但需要很多素数,不要担心限制是否足够高等等。

如另一个答案中所述,你可以通过在代码中将最大“限制”设置为常量来实现这个最终目标,但是对于小范围的素数而言这将是非常低效的,因为剔除将发生在很多比必要的更大的数组。如上所述,除了通过巨大的因素提高效率和减少存储器使用之外,分段还具有允许有效使用多处理的其他优点。但是,使用Take(),TakeWhile(),Where(),Count()等方法不能为大范围的素数提供非常好的性能,因为它们的使用涉及在许多时钟周期内每个元素的许多堆栈方法调用每次通话和返回。但你可以选择使用这些或更多命令式的程序流程,所以这不是一个真正的反对意见。

答案 3 :(得分:1)

我可以尝试解释x和y的作用,但我不认为你可以做你所要求的,而不必从头开始重新启动循环。对于任何“筛选”算法,这几乎都是一样的。

筛子的作用基本上是计算有多少不同的二次方程(偶数或奇数)将每个数作为解。根据n%12的不同,检查每个数字的具体方程是不同的。

例如,当且仅当4 * x ^ 2 + <的解的数量时,具有mod 12余数1或5的数字 n 是素数。 em> y ^ 2 = n 是奇数,数字是无方形的。第一个循环简单地遍历所有可能满足这些不同方程的 x y 的值。通过在每次找到 n 的解决方案时翻转isPrime [ n ],我们可以跟踪解决方案的数量是奇数还是偶数。

问题是我们同时计算所有可能的 n ,这使得这比当时检查一个更有效。只为一些 n 做这件事会花费更多时间(因为你必须确保第一个循环中的n&gt; = lower_limit)并使第二个循环复杂化,因为那个需要知道所有质数小于sqrt。

第二个循环检查该数字是无方形的(没有因子是素数的平方)。

另外,我不认为你对阿特金筛子的实施必然比Eratosthenes的直接筛子更快。然而,最快可能最优化的阿特金筛子将击败最快可能最优化的Eratosthenes筛子。