使用LINQ生成素数

时间:2014-12-11 04:53:34

标签: c# linq optimization parallel-processing primes

以下是面试问题:

以下单行生成并显示前500个素数列表。如何使用并行LINQ优化它,同时仍保持单个C#语句:

MessageBox.Show(string.Join(",", 
    Enumerable.Range(2, (int)(500 * (Math.Log(500) + Math.Log(System.Math.Log(500)) - 0.5)))
                .Where(x => Enumerable.Range(2, x - 2)
                                      .All(y => x % y != 0))
                .TakeWhile((n, index) => index < 500)));

我尝试在查询中引入AsParallel()ParallelEnumerable,但没有看到多核机器带来任何实际好处。查询仍然使用一个CPU核心,而其他核心享受休闲时间。有人可以建议一种改进,将负载平均分配到所有核心,从而减少执行时间吗?

对于发烧友:以下公式返回一个上限,保证大于N个素数,即如果你检查这个数字,你肯定会找到N个素数小于它:

UpperBound = N * (Log(N) + Log(Log(N)) - 0.5) //Log is natural log

4 个答案:

答案 0 :(得分:3)

这在我的机器上很好用。我从未真正见过所有我的核心到目前为止100%。谢谢你给我一个借口玩:)

我增加了数字,直到我有足够的时间来测量(20,000)。

让我与众不同的关键选项是将ExecutionMode设置为ForceParallelism。

因为我使用NotBuffered合并选项,所以当我完成时我会对它进行重新排序。如果您不关心结果的顺序(也许您将结果放在HashSet中),则没有必要这样做。

DegreeOfParallelism和MergeOptions仅为我的机器上的性能提供了微小的收益(如果有的话)。此示例显示如何在单个Linq语句中使用所有选项,这是原始问题。

var numbers = Enumerable.Range(2, (int)(20000 * (Math.Log(20000) + Math.Log(System.Math.Log(20000)) - 0.5)))
                .AsParallel()
                .WithDegreeOfParallelism(Environment.ProcessorCount) 
                .WithExecutionMode(ParallelExecutionMode.ForceParallelism)
                .WithMergeOptions(ParallelMergeOptions.NotBuffered) // remove order dependancy
                .Where(x => Enumerable.Range(2, x - 2)
                                      .All(y => x % y != 0))
                .TakeWhile((n, index) => index < 20000);
string result = String.Join(",",numbers.OrderBy (n => n));

答案 1 :(得分:3)

您只能检查有价值的SQRT(从上面升级的代码)

var numbers = new[] {2, 3}.Union(Enumerable.Range(2, (int) (i*(Math.Log(i) + Math.Log(Math.Log(i)) - 0.5)))
                                           .AsParallel()
                                           .WithDegreeOfParallelism(Environment.ProcessorCount)
                                           // 8 cores on my machine
                                           .WithExecutionMode(ParallelExecutionMode.ForceParallelism)
                                           .WithMergeOptions(ParallelMergeOptions.NotBuffered)
                                           // remove order dependancy
                                           .Where(x => Enumerable.Range(2, (int) Math.Ceiling(Math.Sqrt(x)))
                                                                 .All(y => x%y != 0))
                                           .TakeWhile((n, index) => index < i))
                          .ToList();

但是当你有一个简单快速的讽刺时,它会很疯狂:

private static IEnumerable<int> GetPrimes(int k)
{
    int n = (int)Math.Ceiling((k * (Math.Log(k) + Math.Log(Math.Log(k)) - 0.5)));
    bool[] prime = new bool[n + 1];
    prime[0] = prime[1] = false;
    for (int i = 2; i < prime.Length; i++)
    {
        prime[i] = true;
    }
    for (int i = 2; i*i <= n; ++i) // valid for n < 46340^2 = 2147395600
        if (prime[i])
        {
            for (int j = i*i; j <= n; j += i)
                prime[j] = false;
            yield return i;
        }
}

当然它并不像LINQ那样好,因为它不是解决问题的时髦方法,但你应该知道它存在。

答案 2 :(得分:0)

Stopwatch t = new Stopwatch();
            t.Start();
            var numbers = Enumerable.Range(2, (int)(500 * (Math.Log(500) + Math.Log(System.Math.Log(500)) - 0.5)))
                .Where(x => Enumerable.Range(2, x - 2)
                                      .All(y => x % y != 0))
                .TakeWhile((n, index) => index < 500);
            t.Stop();
            MessageBox.Show(t.ElapsedMilliseconds.ToString());
            MessageBox.Show(string.Join(",", numbers));

它在3毫秒内进行评估。好的linq查询。

答案 3 :(得分:0)

对于未来的读者来说,这就是我最终的目标。它很快。在我简陋的机器上,它会在一秒钟内生成前20,000个素数的列表。

Enumerable.Range(5, (int)(N * (Math.Log(N) + Math.Log(System.Math.Log(N)) - 0.5)))
            .AsParallel()
            .WithDegreeOfParallelism(Environment.ProcessorCount)
            .WithExecutionMode(ParallelExecutionMode.ForceParallelism)
            .WithMergeOptions(ParallelMergeOptions.NotBuffered) // remove order dependancy
            .Where(x => Enumerable.Range(2, (int)Math.Ceiling(Math.Sqrt(x)))
                                  .All(y => x % y != 0))
            .TakeWhile((n, index) => index < N).Concat(new int[] { 2, 3 }.AsParallel()).OrderBy(x => x).Take(N);