PLINQ性能不佳

4

我正在尝试实现PLINQ示例,但遇到以下问题:我的顺序查询比并行查询执行速度更快。

以下是代码示例:

        Stopwatch sw = new Stopwatch();

        int[] vals = Enumerable.Range(0, Int16.MaxValue).ToArray();

        sw.Start();
        int[] x1 = vals.Where(x => x % 2 == 0).ToArray();
        sw.Stop();
        Console.WriteLine("Sequential Execution {0} milliseconds", sw.ElapsedMilliseconds);


        sw.Restart();
        int[] x2 = vals.AsParallel().Where(x => x % 2 == 0).ToArray();
        sw.Stop();
        Console.WriteLine("Parallel Execution {0} milliseconds", sw.ElapsedMilliseconds);

我的电脑是Pentium(R) Dual-Core处理器,我也尝试过Quad-Core AMD Opteron(tm)。

并行查询的结果比顺序查询要慢,请问这是什么问题?

谢谢。


1
请阅读我的推荐阅读内容,以了解如何使用PLINQ处理带有连接的现有LINQ查询。简而言之,您的模数运算太过简单。您需要更复杂的操作。 - Tim Schmelter
3个回答

5

我猜这与一些开销有关。 您迭代的集合非常小(32k个shorts),对这些项执行的操作是微不足道的。

在这种情况下,将集合分区、过滤和重新合并可能比在单个迭代中执行相同操作要昂贵得多。

如果您的比较更加昂贵(例如搜索字符串)并且您的集合增长,您会看到结果发生变化。


是的,PLINQ并非加速每个查询的万能药。它在处理每个项目时存在延迟,例如从数据库加载项目时才能发挥其优势。由于您的查询中没有延迟,并且无论如何都会将CPU最大化,因此PLINQ的开销会使您的速度变慢。 - Jakub Konecki

4

使用PLINQ并不总是明智的选择

PLINQ并不能总是提升速度,而且它始终会增加额外的开销。

从CPU指令的角度来看,无论你需要执行多少工作(称之为 X),你最终都要执行超过 X 的指令。PLINQ会进行大量的额外工作,比如启动线程、将工作委托给它们,并将结果转换为你能够处理的形式。

这样做的好处是可以让多个CPU/Core同时工作。有时它可以提高速度。但当你所需执行的CPU工作相对于开销很小时,它反而会变慢。

运行你的代码时,我得到以下输出:

顺序执行 2 毫秒

并行执行 40 毫秒

此外,我还看到 PLINQ 代码创建了八个工作线程。这八个线程代表了大量的开销,但实际上只执行了 2 毫秒的计算。如果你再次运行并行执行基准测试,就能感受到它所产生的开销。工作线程会一直存在。以下是第二次运行的输出:

顺序执行 2 毫秒

并行 #1 执行 40 毫秒

并行 #2 执行 3 毫秒

第二次运行速度更快了,但仍然比不进行任何操作慢。因为即使工作线程已经创建,PLINQ仍然需要分配操作、将结果返回到可访问的格式中。

你需要执行的工作越多,额外开销对性能影响就越小。在这个例子中,我用名为IsValid的静态函数替换了你的Where lambda,并且计算了500次%2。

static bool IsValid(int input)
{
    int result=0;

    for(int i =0;i<500;i++)            
        result = input%2;

    return result == 0;
}

现在-我的执行时间如下:

顺序执行 36 毫秒

并行 #1 执行 47 毫秒

并行 #2 执行 9 毫秒

您可以看到,在第一次执行上,PLINQ 仍然比较慢,但在第二次执行上显着更快。如果通过将循环从 500 增加到 5000 来增加 CPU 工作(在我的计算机上),PLINQ 轻松获胜。
TL;DR- 您正在做正确的事情;只是您没有做足够的工作使 PLINQ 成为更快的选择。
这是我所做的全部源代码:
static void Main(string[] args)
{
    Stopwatch sw = new Stopwatch();

    int[] vals = Enumerable.Range(0, Int16.MaxValue).ToArray();

    sw.Start();
    int[] x1 = vals.Where(IsValid).ToArray();
    sw.Stop();
    Console.WriteLine("Sequential Execution {0} milliseconds", sw.ElapsedMilliseconds);

    sw.Restart();
    int[] x2 = vals.AsParallel().Where(IsValid).ToArray();
    sw.Stop();
    Console.WriteLine("Parallel #1 Execution {0} milliseconds", sw.ElapsedMilliseconds);

    sw.Restart();
    int[] x3 = vals.AsParallel().Where(IsValid).ToArray();
    sw.Stop();
    Console.WriteLine("Parallel #2 Execution {0} milliseconds", sw.ElapsedMilliseconds);

    Console.Read();
}

static bool IsValid(int input)
{
    int result=0;

    for(int i =0;i<5000;i++)            
        result = input%2;

    return result == 0;
}

你确定第二个并行查询之所以执行更快是因为线程已经启动了吗?如果你尝试连续执行相同的顺序查询,它也会更快,我只是认为这是查询缓存的原因。 - MaRuf

2

这个似乎效果更好:

        Stopwatch sw = new Stopwatch();

        int[] vals = Enumerable.Range(0, 10000000).ToArray();

        sw.Start();
        var x1 = vals.Where(x => x % 2 == 0).ToList();
        sw.Stop();
        Console.WriteLine("Sequential Execution {0} milliseconds", sw.ElapsedMilliseconds);


        sw.Restart();
        var x2 = vals.Where(x => x % 2 == 0).AsParallel().ToList();
        sw.Stop();
        Console.WriteLine("Parallel Execution {0} milliseconds", sw.ElapsedMilliseconds);

不要为200个值启动另一个线程。启动/唤醒其他线程所需的时间比在单个线程上完成整个循环所需的时间更长。+ 更多的线程意味着线程同步机制。
LE:好的,我尝试了Int16.MaxValue,并且在那里运行得更好。现在我意识到最大值约为30k,因此该评论可能不适用于您的情况。可能问题是AsParallel被放错了位置。

我刚刚尝试了一些书中的例子,我知道将集合分解为小量数据的线程会更耗时以进行并行执行,但我没有意识到32K计数的值是不够的。 - Michael Samteladze

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接