PLINQ执行比LINQ更糟糕

Gra*_*ton 13 c# plinq c#-4.0

令人惊讶的是,使用PLINQ并没有为我创建的小测试案例带来好处; 事实上,它甚至比平时更糟糕的LINQ.

这是测试代码:

    int repeatedCount = 10000000;
    private void button1_Click(object sender, EventArgs e)
    {
        var currTime = DateTime.Now;
        var strList = Enumerable.Repeat(10, repeatedCount);
        var result = strList.AsParallel().Sum();

        var currTime2 = DateTime.Now;
        textBox1.Text = (currTime2.Ticks-currTime.Ticks).ToString();

    }

    private void button2_Click(object sender, EventArgs e)
    {
        var currTime = DateTime.Now;
        var strList = Enumerable.Repeat(10, repeatedCount);
        var result = strList.Sum();

        var currTime2 = DateTime.Now;
        textBox2.Text = (currTime2.Ticks - currTime.Ticks).ToString();
    }
Run Code Online (Sandbox Code Playgroud)

结果?

textbox1: 3437500
textbox2: 781250
Run Code Online (Sandbox Code Playgroud)

因此,LINQ比PLINQ花费更少的时间来完成类似的操作!

我究竟做错了什么?还是有一种我不知道的扭曲?

编辑:我已经更新了我的代码以使用秒表,然而,相同的行为仍然存在.为了打折JIT的效果,我实际上尝试了几次点击两者button1并且button2没有特别的顺序.虽然我得到的时间可能不同,但定性行为仍然存在:在这种情况下PLINQ确实较慢.

Dan*_*Tao 22

这是一个经典的错误 - 想一想,"我将运行一个简单的测试来比较这个单线程代码与这个多线程代码的性能."

一个简单的测试是您可以运行以测量多线程性能的最差测试.

通常,并行化某些操作会在您进行并行化的步骤需要大量工作时产生性能优势.当步骤很简单时 - 例如,快速* - 并行化工作的开销最终会使您获得的微小性能提升相形见绌.


考虑一下这个比喻.

你正在建造一座建筑物.如果你有一个工人,他必须逐个铺砖,直到他做了一面墙,然后为下一面墙做同样的事情,依此类推,直到所有的墙都建成并连接起来.这是一项缓慢而费力的任务,可以从并行化中受益.

正确的做到这一点的方法是并行的长城大厦 -租,也就是说,3名工人,每个工人建立了自己的墙,使四壁可以同时建造.找到3名额外的工作人员并为他们分配任务所花费的时间与通过在之前构建1所需的时间内获得4个墙壁所获得的节省相比是微不足道的.

错误的方式做这将是并行的砌砖 -雇用约一千多工人,并有专人负责在同一时间铺设单砖每一个工人.你可能会想,"如果一个工人每分钟可以铺2块砖,那么一千名工人应该能够每分钟铺设2000块砖,所以我马上就能完成这项工作!" 但实际情况是,通过在如此微观的层面上平衡你的工作量,你浪费了大量的能量收集和协调所有工人,为他们分配任务("把这块砖放在那里"),确保没有人工作干扰别人的等等

因此,这种类比的道德是:通常,使用并行化来分割大量工作单元(如墙壁),但保留非实体单元(如砖块)以通常的顺序方式处理.


*因此,通过采用任何快速执行的代码并Thread.Sleep(100)在其末尾添加(或其他一些随机数),您实际上可以在更加工作密集的上下文中很好地近似并行化的性能增益.每次迭代突然顺序执行此代码的速度将减慢100 ms,而并行执行速度将显着降低.


Jus*_*ner 21

第一:停止使用DateTime来测量运行时间.请改用秒表.测试代码如下所示:

var watch = new Stopwatch();

var strList = Enumerable.Repeat(10, 10000000);

watch.Start();
var result = strList.Sum();
watch.Stop();

Console.WriteLine("Linear: {0}", watch.ElapsedMilliseconds);

watch.Reset();

watch.Start();
var parallelResult = strList.AsParallel().Sum();
watch.Stop();

Console.WriteLine("Parallel: {0}", watch.ElapsedMilliseconds);

Console.ReadKey();
Run Code Online (Sandbox Code Playgroud)

第二:并行运行会增加开销.在这种情况下,PLINQ必须找出划分集合的最佳方法,以便它可以安全地并行地对元素求和.之后,您需要加入创建的各种线程的结果并将它们相加.这不是一项微不足道的任务.

使用上面的代码,我可以看到使用Sum()可以调用~95ms.调用.AsParallel().Sum()网络约为185ms.

如果通过这样做获得某些东西,那么在并行中执行任务只是一个好主意.在这种情况下,Sum是一个足够简单的任务,使用PLINQ无法获得.

  • +1 @Justin Niessner 非常清晰的答案。@Ngu Soon Hui PLINQ 会很方便,但如果你不能很好地理解它,它也会很邪恶。我建议您在开始使用 PLINQ 之前先查看缓存错误共享和 GC 限制。 (2认同)

Jon*_*eet 8

其他人指出了你的基准测试中的一些缺陷.这是一个简短的控制台应用程序,使其更简单:

using System;
using System.Diagnostics;
using System.Linq;

public class Test
{
    const int Iterations = 1000000000;

    static void Main()
    {
        // Make sure everything's JITted
        Time(Sequential, 1);
        Time(Parallel, 1);
        Time(Parallel2, 1);
        // Now run the real tests
        Time(Sequential, Iterations);
        Time(Parallel,   Iterations);
        Time(Parallel2,  Iterations);
    }

    static void Time(Func<int, int> action, int count)
    {
        GC.Collect();
        Stopwatch sw = Stopwatch.StartNew();
        int check = action(count);
        if (count != check)
        {
            Console.WriteLine("Check for {0} failed!", action.Method.Name);
        }
        sw.Stop();
        Console.WriteLine("Time for {0} with count={1}: {2}ms",
                          action.Method.Name, count,
                          (long) sw.ElapsedMilliseconds);
    }

    static int Sequential(int count)
    {
        var strList = Enumerable.Repeat(1, count);
        return strList.Sum();
    }

    static int Parallel(int count)
    {
        var strList = Enumerable.Repeat(1, count);
        return strList.AsParallel().Sum();
    }

    static int Parallel2(int count)
    {
        var strList = ParallelEnumerable.Repeat(1, count);
        return strList.Sum();
    }
}
Run Code Online (Sandbox Code Playgroud)

汇编:

csc /o+ /debug- Test.cs
Run Code Online (Sandbox Code Playgroud)

在我的四核i7笔记本电脑上的结果; 快速运行最多2个核心,或者更慢地运行4个核心.基本上是ParallelEnumerable.Repeat胜利,其次是序列版本,然后是正常的并行化Enumerable.Repeat.

Time for Sequential with count=1: 117ms
Time for Parallel with count=1: 181ms
Time for Parallel2 with count=1: 12ms
Time for Sequential with count=1000000000: 9152ms
Time for Parallel with count=1000000000: 44144ms
Time for Parallel2 with count=1000000000: 3154ms
Run Code Online (Sandbox Code Playgroud)

请注意,这个答案的早期版本由于错误的元素数量而令人尴尬地存在缺陷 - 我对上述结果更有信心.