C# 在迭代快结束时,Parallel.ForEach的速度会减慢

C# 在迭代快结束时,Parallel.ForEach的速度会减慢,c#,performance,parallel.foreach,C#,Performance,Parallel.foreach,我有以下问题: 我将parallel.foreach迭代用于相当CPU密集型的工作负载(对许多项应用一种方法)&对于前80%的项,它工作得很好-使用所有CPU核非常好 随着迭代似乎接近尾声(我会说大约80%),我看到线程的数量开始一个核一个核地减少,最后大约5%的项目只由两个核处理。因此,为了避免在迭代结束前使用所有内核,它在迭代结束时非常缓慢 请注意,每个项目的工作量可能非常不同。其中一项可能持续1-2秒,另一项可能需要2-3分钟才能完成 任何想法,建议都是非常受欢迎的 使用的代码: var

我有以下问题:

我将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}
}
var source=myList.ToArray();
var rangePartitioner=Partitioner.Create(0,source.Lenght);
使用(SqlConnection=newsqlconnection(cnStr))
{
connection.Open();
尝试
(
Parallel.ForEach(rangePartitioner,(range,loopState)=>
{

对于(int i=range.Item1;i这是一个不可避免的结果,因为并行性是每次计算的结果。很明显,整个并行批处理的运行速度不能超过工作集中最慢的单个项所花费的时间


想象一批100个项目,其中8个比较慢(比如说运行1000秒),其余的比较快(比如运行1秒)。你在8个线程中以随机顺序启动它们。很明显,最终每个线程将计算一个长期运行的项目,此时你会看到充分的利用率。最终是一个(s)首先命中其长op的将完成其长op,并快速完成剩余的短op。此时,您只有部分长op等待完成,因此您将看到活跃利用率下降。即,在某个点上,只剩下3个op需要完成,因此只有3个内核在使用


缓解策略

  • 您的长时间运行的项目可能适合于“内部并行”,从而使它们具有更快的最小运行限制
  • 您的长时间运行的项目可能能够被识别并优先排序,以便首先开始(这将确保您尽可能长时间地充分利用CPU)
  • (请参阅下面的更新)不要在主体可以长时间运行的情况下使用分区,因为这只会增加此效果的“命中率”(即完全删除rangePartitioner)。这将大大减少此效果对特定循环的影响
无论哪种方式,批处理运行时都受到批处理中最慢项的运行时的约束



更新我还注意到你在循环中使用分区,这大大增加了这种效果的范围,也就是说,你说“将这个工作集分解为N个工作集”,然后并行运行这N个工作集。在上面的例子中,这可能意味着你得到(比如)3个长操作进入同一工作集,因此这些操作将在同一线程上处理。因此,如果内部主体可以长时间运行,则不应使用分区。例如,关于分区的文档说,如果有多个线程处理相同数量的项目每个项目都需要不同的时间,当然,您会有一些线程提前完成

如果使用大小未知的集合,则将逐个获取项目:

var source = myList.AsEnumerable();
另一种方法可以是生产者-消费者模式

你想用这段代码做什么?你想通过一个连接“并行”执行SQL语句吗?你为什么认为“并行执行”是错误的是否有助于解决SQL性能问题?@LasseV.Karlsen请注意SqlConnection。我怀疑OP试图通过强制执行SQL命令来解决SQL性能问题,这当然会导致相反的结果,因为阻塞这看起来像是一个实例。OP存在SQL性能问题,并假设可以通过在中执行命令来解决并行。如果失败,OP会询问
parallel.ForEach
而不是实际问题,应该回答实际问题,例如使用SqlBulkCopy、TVPs、重写低效的SQL语句或将多个命令批处理到one@Horia您误解了。每个单独的命令都需要一些时间才能转到服务器并返回。单个b不过,atched命令避免了过多的往返。在任何情况下,在ETL场景中,您都在处理数据流。这意味着您需要(例如)几个工人在管道中以CPU最大值处理数据。SSIS就是这样做的。TPL数据流也是这样做的。
Parallel.ForEach
虽然只是在X部分分割输入ns可能不平衡。如果其中一个分区干涸,相关任务将闲置。不要使用分区,因为每个迭代中的工作负载都很高。