关于c#:Eratosthenes优化筛


Sieve of Eratosthenes Optimization

我编写了自己的程序,使用Eratosthenes筛子从2-n查找素数。 有什么方法可以实施更有效的方法来删除复合数字?

链接到项目:https://github.com/Gurran/Sieve-of-Eratosthenes

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
class Program
{
    static void Main()
    {
        int max;

        Console.Write("Enter max number:");
        max = int.Parse(Console.ReadLine());
        FindPrimes(max);
        Console.ReadLine();
    }

    // Prints numbers.
    public static void PrintMap(List<int> List)
    {
        for (int i = 0; i < List.Count; i++)
        {
            if (i % 10 == 0)
                Console.WriteLine();
            Console.Write(List.ElementAt(i) +",");
        }
    }

    // Generates list containing 2 - max
    // Removes non-primes
    // Calls PrintMap method
    public static void FindPrimes(int max)
    {
        int x = 2;
        List<int> NrRange = new List<int>();

        for (int i = 2; i < max; i++)
            NrRange.Add(i);
        for (int i = 0; i < NrRange.Count; i++)

            for (int j = x; j < NrRange.Count; j++)
                if (NrRange.ElementAt(j) % x == 0)
                    NrRange.RemoveAt(j);
            x++;
        PrintMap(NrRange);
    }
}


我运行了您的例行程序FindPrimes(100),但结果错误:

2, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25,
27, .. 95, 97, 99

让我们以不同的方式编写它:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// If we put"FindPrimes", let's return them: List<int> instead of void
public static List<int> FindPrimes(int max) {
  if (max < 2)
    return new List<int>();

  // Try avoid explict adding .Add(), but generating
  List<int> result = Enumerable
    .Range(2, max - 1)
    .ToList();

  // sieving composite numbers out the initial list
  for (int i = 1; i < result.Count; ++i) {
    int prime = result[i - 1];

    // removing all numbers that can be divided by prime found
    // when removing we should loop backward
    for (int j = result.Count - 1; j > i; --j)
      if (result[j] % prime == 0)
        result.RemoveAt(j);
  }

  return result;
}

测试

1
 Console.Write(String.Join(",", FindPrimes(100)));

结果:

2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, ..., 83, 89, 97


您可以在C#中对Eratosthenes的基本筛进行许多优化,这对于数十亿至数万亿美元的较大范围非常重要,包括分段技术以减少内存使用并增加缓存局部性,多处理,滚轮分解以减少内存数量。复合数字剔除和复合筛分模式以减少剔除循环的复杂性,我在以下答案中涵盖了许多内容:如何使用多线程C#实现Eratosthenes筛网?由于消除了车轮上的复合材料,这些技术将每个复合材料数量的调用次数减少到比每个复合材料之一理想值的比率低得多(约为筛分范围的四分之一到十亿),而时间是在使用的内核数之间平均分配。

还有一种替代方法,可以通过一种替代模式来过滤复合材料,从而将复合体的速度提高大约两倍,但我尚未发布。由于降低了复杂性而无需查找最里面的剔除循环的表,因此速度更快。

使用另一种语言(例如C / C ++,Rust,Nim,Haskell等)可产生更高效的机器代码,并且除了所有其他技术外,还允许对复合数字剔除循环进行主要展开,这再次变得更快,因为从现代台式机CPU上的大约10个时钟周期开始,它可以将每个复合数字清除所需的时间减少到一个时钟周期以上。因此,在3.5 GHz / 4核CPU(例如我使用的Intel i7-2700)上,将复合数字剔除到十亿范围所需的时间可以减少到约63毫秒。如果您想要的不是底涂数量的计数,那么时间当然会增加。

所使用的技术与Kim Walich的开源C ++ primesieve(https://primesieve.org)相似,不同之处在于新模式会由于车轮分解系数的增加和复杂性的降低而使其速度稍快,但是您可能会发现他的源代码难以阅读和理解(除非您是数学家,否则您可能难以理解车轮分解模式的代码)。但是,您应该能够运行他的程序以查看可能的情况。

如前所述,您的程序不是真正的Eratosthenes筛,而是效率低下的Trial by Division(经更正)版本。由于您可能不会欣赏我其他解决方案的复杂性(请先运行,然后运行),请查看以下C#无边界筛网中的最后一个: rosettacode.org/Sieve_of_Eratosthenes#Unbounded,其功能在某些方面与您的代码相同,但使用带零位打包(每个奇数试用位一位)的分段Eratosthenes筛子,其中仅奇数为2是唯一的偶数质数。

我所链接到的任何程序都将比您的程序快很多倍,即使在固定的范围之外(即使范围很小)也是如此。


我的代码:https://github.com/ktprime/ktprime/blob/master/PrimeNumber.cpp
基准如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
                          i3-350M,i5-3470,i7-7500u,i7-6700,r7-1700
Pi(0,    1e10) = 455052511    3.10   1.84    1.55     1.32    1.65
Pi(1e11, 1e10) = 394050419    4.35   2.50    2.02     1.78    2.00
Pi(1e12, 1e10) = 361840208    5.30   3.00    2.40     2.04    2.25
Pi(1e13, 1e10) = 334067230    6.52   3.50    2.85     2.39    2.67
Pi(1e14, 1e10) = 310208140    7.90   4.20    3.50     2.87    3.20
Pi(1e15, 1e10) = 289531946    9.90   5.10    4.32     3.49    3.91
Pi(1e16, 1e10) = 271425366    11.7   6.10    5.11     4.12    4.73
Pi(1e17, 1e10) = 255481287    13.9   7.09    5.95     4.84    5.63
Pi(1e18, 1e10) = 241272176    17.2   8.58    7.17     5.82    6.88
Pi(1e19, 1e10) = 228568014    24.0   11.6    9.86     8.00    9.50
Pi(0-1e9,10^9) = 22537866     8.15   4.28    3.92     3.02    3.64
Pi(1e18, 10^6) = 24280        0.65   0.46    0.34     0.48    0.60
Pi(1e18, 10^8) = 2414886      1.30   0.81    0.70     0.60    0.70
Pi(1e18, 10^9) = 24217085     3.50   1.80    1.58     1.26    1.50
Pi(0,    1e12) = 37607912018  500    270     224      200     220
Pi(1e14, 1e12) = 31016203073  790    420     354      295     320
Pi(1e16, 1e12) = 27143405794  1160   600     512      420     485
Pi(1e18, 1e12) = 24127637783  1500   760     622      520     640
Pi(1e19, 1e12) = 22857444126  1700   830     702      600     665