将Parallel.ForEach与foreach进行比较时的特殊结果

时间:2015-02-11 11:32:15

标签: c# wpf c#-4.0 foreach parallel.foreach

我一直在尝试优化我的WPF应用程序的性能,其中将从服务器下载几乎 40K联系人,并且将在应用程序中创建完整的分层树。用户只能从视图中的树中搜索。现有代码使用了大量 foreach 循环,这需要花费大量时间(创建视图时只需5-6秒);所以我想修改代码,我做的第一件事就是用 Paralle.ForEach 替换 foreach 循环。我创建了一个非常非常的示例应用程序来测试两者之间的差异。

Parallel.ForEach

      Parallel.ForEach(lstUsers, item =>
      {
          Console.WriteLine(item.ToString());
      });

正常的foreach

      foreach (var item in lstUsers)
      {
          Console.WriteLine(item.ToString());
      }

我针对1000个联系人运行了应用程序,令我惊讶的是结果如下:

Parallel.ForEach - > 210毫秒

foreach - > 104毫秒

我认为 Parallel.ForEach 应该比 foreach 循环花费更少的时间,但我看到正好相反。看起来我完全错了。

2 个答案:

答案 0 :(得分:1)

您证明此算法(Console.Writeline)不适合并行化。在多个线程上运行它的开销(这不是微不足道的)不能通过并行运行节省的时间来赢回

答案 1 :(得分:0)

我强烈建议您下载并阅读Microsoft" Patterns for Parallel Programming"的免费书籍。你正在做的是他们在"反模式"中所涵盖的一个例子。部分(人们倾向于做但却不好的模式)

  

非常小的环体

     

如前所述,Parallel类在a中实现   方式以便在产生的同时提供高质量的负载平衡   尽可能少开销。但是仍有开销。该   Parallel.For产生的开销主要集中在两个方面   成本:

     
      
  1. 委派调用。如果你眯着眼看前面Parallel.For的例子,对Parallel.For的调用看起来很像C#   循环或Visual Basic For循环。不要被愚弄:它仍然是一种方法   呼叫。这样做的一个结果是Parallel.For的“身体”   “loop”作为委托提供给方法调用。调用一个   委托产生与虚拟成本大致相同的成本   方法调用。
  2.   
  3. 线程之间的同步以实现负载平衡。尽管这些成本尽可能地降低,但任何负载量都会降低   平衡会产生一些成本,并且使用的负载平衡越多,   需要更多同步。
  4.         

    对于中到大型环体,这些成本在很大程度上可以忽略不计。   但随着循环体的大小减小,开销变得越来越大   更引人注目。对于非常小的物体,环路可以完全   由此开销的成本占主导地位。支持非常的并行化   小循环体需要解决上面的#1和#2。

         

    这种模式包括将输入分块到范围中,然后   而不是用并行循环替换顺序循环,包装   带有并行循环的顺序循环。

         

    System.Concurrent.Collections.Partitioner类提供了一个   Create方法重载,接受一个整数范围并返回一个   OrderablePartitioner<Tuple<Int32,Int32>>Int64的变体   而不是Int32也可用):

    public static OrderablePartitioner<Tuple<long, long>> Create(
        long fromInclusive, long toExclusive);
    
         

    超载Parallel.ForEach接受Partitioner<T>的实例   和OrderablePartitioner<T>作为来源,允许你通过   调用Partitioner.Create来调用的结果   Parallel.ForEach。现在,想想Partitioner<T>和。{   OrderablePartitioner<T>IEnumerable<T>

         

    Tuple<Int32,Int32>表示从包含值到的范围   独家价值。考虑以下顺序循环:

    for (int i = from; i < to; i++)
    {
        // ... Process i.
    }
    
         

    我们可以使用Parallel.For并行化,如下所示:

    Parallel.For(from, to, i =>
    {
        // ... Process i.
    });
    
         

    或者,我们可以使用Parallel.ForEach来调用   Partitioner.Create,在整个范围内包含一个连续循环   在Tuple<Int32, Int32>中提供,inclusiveLowerBound   由元组的Item1代表   exclusiveUpperBound由元组的Item2

    表示
    Parallel.ForEach(Partitioner.Create(from, to), range =>
    {
        for (int i = range.Item1; i < range.Item2; i++)
        {
            // ... process i
        }
    });
    
         

    虽然更复杂,但这使我们能够处理非常小的事情   循环体通过避开一些上述成本。而不是   为每个主体调用调用一个委托,我们现在正在分摊   委托调用的成本跨越分块中的所有元素   范围。另外,就并行循环而言,存在   只是要处理的几个元素:每个范围,而不是每个   指数。这隐含地降低了同步成本,因为   负载均衡的元素较少。

         

    虽然Parallel.For应被视为最佳选择   并行化循环,如果性能测量显示   加速没有实现或者它们比预期的要小,   您可以尝试使用Parallel.ForEach中显示的方法   与Partitioner.Create合作。

另外,我考虑写Console作为I / O的一种形式,所以我也建议阅读反模式部分&#34; PARALLEL LOOPS for I / O-BOUND WORKLOAD IN SCALABLE应用&#34;