Parallel.ForEach减慢到迭代结束

时间:2016-09-26 09:15:07

标签: c# performance parallel.foreach

我有以下问题:

我正在使用parallel.foreach迭代来处理相当大的CPU密集型工作负载(对多个项目应用方法)&它适用于前80%的项目 - 使用所有cpu内核非常好。

由于迭代似乎接近结束(我会说约80%),我看到线程的数量开始逐渐减少核心,&最后,大约5%的项目仅由两个核心进行处理。所以尽量使用所有核心直到最后,它会在迭代结束时变得非常困难。

请注意,每个项目的工作量可能非常不同。一个可以持续1-2秒,另一个项目可能需要2-3分钟才能完成。

任何想法,建议都是非常受欢迎的。

使用的代码:

var source = myList.ToArray();
var rangePartitioner = Partitioner.Create(0, source.Lenght);
using (SqlConnection connection =new SqlConnection(cnStr))
{
   connection.Open();
   try
   (
      Parallel.ForEach(rangePartitioner, (range, loopState) =>
      {
         for(int i = range.Item1; i<range.Item2; i++)
         {
            CPUIntensiveMethod(source[i]);
         }
       });
   }
   catch(AggretateException ae)
   { //Exception cachting}
}

2 个答案:

答案 0 :(得分:0)

这是并行性每次计算这一事实的不可避免的结果。很明显,整个并行批处理的运行速度不会超过工作集中最慢的单个项目所用的时间。

想象一下100件物品,其中8件很慢(比如运行1000秒),其余的很快(比如运行1秒)。你可以通过8个线程以随机顺序启动它们。很明显,最终每个线程将计算一个长期运行的项目,此时您将看到完全利用。最终,首先击中长期操作的那些人将完成他们的长操作并快速完成任何剩余的短操作。那时你只有一些长操作等待完成,所以你会看到活动利用率下降...即在某些时候只剩下3个操作完成,所以只有3个核心在使用。

缓解策略

  • 您的长期运行项目可能适合于内部并行机制&#39;允许他们拥有更快的最小限制运行时间。
  • 您可以识别长时间运行的项目并确定优先级以便首先启动(这将确保您尽可能长时间地获得完整的CPU利用率)
  • (请参阅下面的更新)在身体可以长时间运行的情况下不要使用分区,因为这样可以简化“点击”。这种效果。 (即完全摆脱你的rangePartitioner)。这将大大减少此效果对您的特定循环的影响

无论哪种方式,批处理运行时都受到批处理中最慢项的运行时限的约束。

更新我也注意到你在你的循环上使用了分区,这大大增加了这种效果的范围,即你说要将这个工作集分解为N个工作 - 设定&#39;然后并行化这N个工作集的运行。在上面的示例中,这可能意味着您将(例如)3个长操作集合到同一个工作集中,因此这些操作将在同一个线程上进行处理。因此,如果内部主体可以长时间运行,则 NOT 应该使用分区。例如,这里的分区文档https://msdn.microsoft.com/en-us/library/dd560853(v=vs.110).aspx表示这是针对短体的

答案 1 :(得分:0)

如果您有多个线程处理每个项目的相同数量,并且每个项目花费不同的时间,那么您当然会有一些先前完成的线程。

如果您使用的尺寸未知,那么这些物品将逐一拍摄:

var source = myList.AsEnumerable();

另一种方法可以是生产者 - 消费者模式 https://msdn.microsoft.com/en-us/library/dd997371