【问题标题】:Parallel.ForEach slows down towards end of the iterationParallel.ForEach 在迭代结束时变慢
【发布时间】:2016-09-26 09:15:06
【问题描述】:

我有以下问题:

我正在对相当 CPU 密集型工作负载使用 parallel.foreach 迭代(对多个项目应用一种方法)并且它对大约前 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}
}

【问题讨论】:

  • 你想用这段代码做什么?您是否试图通过 single 连接“并行”执行 SQL 语句?为什么您认为“并行执行”有助于解决 SQL 性能问题?
  • @LasseV.Karlsen 注意 SqlConnection。我怀疑 OP 正试图通过暴力破解 sql 命令来解决 sql 性能问题,这当然会导致相反的结果,因为阻塞
  • 这看起来像the XY Problem 的情况。 OP 存在 SQL 性能问题,并假设可以通过并行执行命令来修复它。当失败时,OP 会询问Parallel.ForEach 而不是 actual 问题,应该回答这个问题,例如使用 SqlBulkCopy、TVP、重写低效的 SQL 语句或将多个命令批处理为一个
  • @Horia 你误会了。每个单独的命令都需要一些时间才能到达服务器并返回。单个批处理命令虽然避免了过多的往返。在任何情况下,在 ETL 场景中,您都在处理 数据。这意味着您需要例如管道中的一些工作人员以 CPU 的最大值处理数据。这就是 SSIS 所做的。这也是 TPL 数据流的作用。 Parallel.ForEach 虽然只是将输入拆分为可能平衡的 X 分区。如果其中一个分区干涸,相关任务将闲置。
  • 不要使用分区,因为每次迭代的工作量都很大。

标签: c# performance parallel.foreach


【解决方案1】:

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


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


缓解策略

  • 您的长期运行项目可能适合“内部并行”,从而使它们具有更快的最小限制运行时间。
  • 您的长时间运行的项目可能能够被识别并优先启动(这将确保您尽可能长时间地获得 CPU 的充分利用)
  • (请参阅下面的更新)不要在主体可以长时间运行的情况下使用分区,因为这只会增加这种效果的“命中”。 (即完全摆脱你的 rangePartitioner )。这将大大减少这种效果对您的特定循环的影响

无论哪种方式,您的批处理运行时间都受批处理中最慢项目的运行时间限制。


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

【讨论】:

    【解决方案2】:

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

    如果你使用不知道大小的collection,那么item会被一一取走:

    var source = myList.AsEnumerable();
    

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-05-04
      • 2013-04-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-08-16
      • 1970-01-01
      相关资源
      最近更新 更多