为什么我的AsOrdered PLINQ查询比我的无序查询更快

时间:2012-06-06 00:10:47

标签: c# .net plinq

我写了一些基本的示例代码来熟悉PLINQ。

我遇到了一些奇怪的东西。我不知道我的代码中是错误还是我对PLINQ的理解错误。

MSDN文档指出,添加AsOrdered()将以可能的性能成本保留调用的顺序。

我写了一些单元测试,并注意到文档中所述的对结果集的顺序的影响。但我已经看到了对性能的负面影响。

以下是我的方法:

public IEnumerable<int> ParallelCalculatePrimesUpTo(int maxValue)
{
    return from number in Enumerable.Range(1, maxValue).AsParallel()
            where IsPrime(number)
            select number;
}

public IEnumerable<int> OrderedParallelCalculatePrimesUpTo(int maxValue)
{
    return from number in Enumerable.Range(1, maxValue).AsParallel().AsOrdered()
            where IsPrime(number)
            select number;
}

我非常简单的基准

    [TestMethod]
    public void SimplisticBenchmark6()
    {
        var primeNumberCalculator = new PrimeNumberCalculator();

        var startTime = DateTime.Now;

        primeNumberCalculator.ParallelCalculatePrimesUpTo(10000000).ToList();

        var totalTime = DateTime.Now - startTime;

        Console.WriteLine(totalTime);
    }

    [TestMethod]
    public void SimplisticBenchmark7()
    {
        var primeNumberCalculator = new PrimeNumberCalculator();

        var startTime = DateTime.Now;

        primeNumberCalculator.OrderedParallelCalculatePrimesUpTo(10000000).ToList();

        var totalTime = DateTime.Now - startTime;

        Console.WriteLine(totalTime);
    }

无论我多久运行一次这个测试,有序版本都会打败无序版本。我的四核计算机上的订购速度大约快4秒。对于有序的一个,我得到大约18秒,对于无序的一个,我得到22秒。我在两天的时间内完成了几十次测试(在那些日子之间重新启动)。

如果我将数字10 000 000减至6 000 000,差异仍然存在,但不太明显,如果我将其降低至3 000 000,则速度大致相同。

我尝试按执行顺序运行测试,结果是一样的。

以下是在PLINQ查询中调用的IsPrime方法:

// uses inneficient trial division algorithm
private bool IsPrime(int number)
{
    if (number == 1)
        return false;

    for (int divisor = 2; divisor <= Math.Sqrt(number); divisor++)
    {
        if (number % divisor == 0)
            return false;
    }

    return true;
}

是什么解释了这个?

2 个答案:

答案 0 :(得分:4)

您是否始终以相同的顺序运行测试?

我在我的机器上重新创建了你的结果,我发现,无论如何,'Ordered'结果更快。我使用稍微修改过的代码进行基准测试:

static void Main(string[] args)
{
    const int size = 9000000;
    BenchIt("Parallel", ParallelCalculatePrimesUpTo, size);
    BenchIt("Ordered ", OrderedParallelCalculatePrimesUpTo, size);
    Console.ReadKey();
}

public static void BenchIt(string desc, Func<int, IEnumerable<int>> myFunc, int size)
{
    var sw = new Stopwatch();            
    sw.Restart();
    myFunc.Invoke(size).ToList();
    sw.Stop();
    Console.WriteLine("{0} {1}",desc, sw.Elapsed);
}

我的结果最初表明你是对的。有序的方法更快。但是,如果我SWAPPED调用的顺序,我发现非有序方法更快。换句话说,无论哪一个更快。据推测,由于任务并行库正在进行的线程池管理。

但是 - 我机器上两者之间的差异非常小。远远没有你看到的差异。

您的硬件是什么样的?

PLINQ做了一些关于如何执行最快的猜测。在这种情况下,我不知道这是否会直接帮助你;但是你可能想在IsPrime中间设置一个断点并在几百次迭代后停止它并检查线程窗口。

执行ParallelCalculatedPrimesUpTo经文OrderedParallelCalculatePrimesUpTo时有多少个主题?我到了这里;但它可能会决定您机器上的不同值,从而产生您所看到的意外时间。在我的机器上 - 我每次都得到8个线程 - 但我的时间几乎完全相同 - 无论哪个被称为第一个都因为这些线程的创建而变慢。但是你不能保证特定数量的线程(你可以设置最大值,但不能强制它们被使用)。

答案 1 :(得分:1)

您能告诉我们4个不同内核的CPU利用率是多少? AsOrdered()可能会强制在同一个核心上发生更多顺序调用。通过改进的局部性,硅级缓存和分支预测可能对您有利。

另一种可能性是,在使用AsOrdered()投影时,对于单调递增整数(int.Range)的情况,.NET框架中有一些优化。我不确定这是怎么回事,但这是可能的。

一个有趣的比较测试是以随机顺序生成第三组数字(显然,你必须提前将它们随机化,然后再处理三个数组)。然后看看它是否与它有关?