并行聚合不捕获所有迭代

时间:2014-01-16 00:15:11

标签: c# parallel-processing locking

我的代码使用简单的For循环很有效,但我正在努力加快速度。我正在尝试调整代码以使用多个内核并登陆Parallel For。

在高级别,我正在从CalcRoutine收集数千个帐户的结果,并将结果存储在包含6个元素的数组中。然后我重新运行这个过程1000次。每个6元素数组中元素的顺序很重要,但这6个元素数组的最后1,000次迭代的顺序并不重要。当我使用For循环运行代码时,我得到一个6,000个元素长的列表。但是,当我尝试Parallel For版本时,我得到了接近600的东西。我已经确认“返回localResults”行被调用了1000次,但由于某种原因,并非所有6个元素数组都被添加到列表中TotalResults 。任何关于为什么这不起作用的见解将不胜感激。

object locker = new object();

Parallel.For(0, iScenarios, () => new double[6], (int k, ParallelLoopState state, double[] localResults) =>
            {
                List<double> CalcResults = new List<double>();
                for (int n = iStart; n < iEnd; n++)
                {
                    CalcResults.AddRange(CalcRoutine(n, k));
                }
                localResults = this.SumOfResults(CalcResults);
                return localResults;                   
            },
             (double[] localResults) =>
             {                    
                 lock (locker)
                {
                     TotalResults.AddRange(localResults);
                 }
             });

编辑:这是“非平行”版本:

for (int k = 0; k < iScenarios; k++)            
{
          CalcResults.Clear();                
          for (int n = iStart; n < iEnd; n++)                
          {
              CalcResults.AddRange(CalcRoutine(n, k));                    
          }             
          TotalResults.AddRange(SumOfResults(CalcResults));
 }

1场景的输出是6个双打的列表,2个场景是12个双打的列表,... n场景6n加倍。

同样对于其中一个问题,我检查了“TotalResults.AddRange ...”被调用的次数,并且不是完整的1000次。为什么每次都不会这个叫?使用锁定,每个线程不应该等待此部分可用吗?

2 个答案:

答案 0 :(得分:2)

查看Parallel.For

的文档
  

这些初始状态将传递给每个任务的第一个 body 调用。然后,每个后续的 body 调用都会返回一个可能已修改的状态值,该值将传递给下一个 body 调用。最后,每个任务的最后一个 body 调用返回一个传递给 localFinally 委托的状态值

但是你的 body 委托忽略了localResults传入值,该值返回了此任务中的上一次迭代。将循环状态作为数组使得编写正确的版本变得棘手。这会有效,但看起来很乱:

//EDIT - Create an array of length 0 here    V    for input to first iteration
Parallel.For(0, iScenarios, () => new double[0],
    (int k, ParallelLoopState state, double[] localResults) =>
        {
            List<double> CalcResults = new List<double>();
            for (int n = iStart; n < iEnd; n++)
            {
                CalcResults.AddRange(CalcRoutine(n, k));
            }
            localResults = localResults.Concat(
                               this.SumOfResults(CalcResults)
                           ).ToArray();
            return localResults;                   
        },
         (double[] localResults) =>
         {                    
             lock (locker)
            {
                 TotalResults.AddRange(localResults);
             }
         });

(假设Linq的可枚举扩展名在范围内,适用于Concat

我建议使用不同的数据结构(例如List<double>而不是double[])来表示更自然地允许添加更多元素的状态 - 但这意味着要更改{{ 1}}你没有表现出来。或者只是让它更加抽象:

SumOfResults

(如果它按照你似乎已经假设的方式工作,为什么他们会提供两个单独的委托,如果它完成了,从 body 返回,就是立即调用 localFinally 带有返回值?)

答案 1 :(得分:0)

试试这个:

using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

class Program
{
    static void Main(string[] args)
    {
        var iScenarios = 6;
        var iStart = 0;
        var iEnd = 1000;
        var totalResults = new List<double>();

        Parallel.For(0, iScenarios, k => {
            List<double> calcResults = new List<double>();
            for (int n = iStart; n < iEnd; n++)
                calcResults.AddRange(CalcRoutine(n, k));
            lock (totalResults)
            {
                totalResults.AddRange(calcResults);
            }
        });           

    }

    static IEnumerable<double> CalcRoutine(int a, int b)
    {
        yield return 0;
    }

    static double[] SumOfResults(IEnumerable<double> source)
    {
        return source.ToArray();
    }
}