并行化数据处理

时间:2014-01-08 21:55:44

标签: c# c#-4.0 parallel-processing task-parallel-library

我正在努力改善我正在做的一些数据处理的运行时间。数据最初是各种集合(Dictionary,但其他一些IEnumerable类型),处理的最终结果应为Dictionary<DataType, List<DataPoint>>

我把所有这些工作都很好......除了需要一个小时才能运行,它需要在20分钟内运行。虽然它们经常交叉引用其他集合,但是没有任何数据与同一集合中的任何其他数据有任何连接,因此我认为我应该将其并行化。

处理的主要结构有两级循环,其间有一些处理:

// Custom class, 0.01%
var primaryData= GETPRIMARY().ToDictionary(x => x.ID, x => x);

// Custom class, 11.30%
var data1 = GETDATAONE().GroupBy(x => x.Category)
                        .ToDictionary(x => x.Key, x => x);  

// DataRows, 8.19%
var data2 = GETDATATWO().GroupBy(x => x.Type)
                        .ToDictionary(x => x.Key, x => x.OrderBy(y => y.ID));

foreach (var key in listOfKeys)
{
   // 0.01%
   var subData1 = data1[key].ToDictionary(x => x.ID, x => x);

   // 1.99%
   var subData2 = data2.GroupBy(x => x.ID)
                       .Where(x => primaryData.ContainsKey(x.Type))
                       .ToDictionary(x => x.Key, x => ProcessDataTwo(x, primaryData[x.Key]));

   // 0.70%
   var grouped = primaryData.Select(x => new { ID = x.Key, 
                                               Data1 = subData1[x.Key],
                                               Data2 = subData2[x.Key] }).ToList();
   foreach (var item in grouped)
   {
       // 62.12%
       item.Data1.Results = new Results(item.ID, item.Data2);
       // 12.37%
       item.Data1.Status = new Status(item.ID, item.Data2);
   }
   results.Add(key, grouped);
}
return results;

listOfKeys非常小,但每个grouped都有几千个项目。 如何构建此功能,以便每次调用item.Data1.Process(item.Data2)都可以排队等待并执行?

根据我的个人资料管理器,所有ToDictionary()个电话一起约占21%,ToList()占0.7%,内部foreach内的两个项目合在一起占74%。因此,我为什么要把重点放在那里。

我不知道是否应该使用Parallel.ForEach()来替换外部foreach,内部{,2},或者是否应该使用其他结构。我也不确定我是否可以对数据(或持有它的结构)做些什么来改进对它的并行访问。

(请注意,我坚持使用.NET4,因此无法访问asyncawait

2 个答案:

答案 0 :(得分:1)

修改

考虑到在我写这个答案后提供的时间测量,似乎这种方法是在错误的地方寻找节省。我会把我的答案留给没有测量的优化警告!!!


因此,由于您的方法的嵌套性,您正在导致一些不必要的过度迭代,导致相当讨厌的Big O特征。

这可以通过使用ILookup接口按键预先对集合进行分组并使用这些来代替重复且昂贵的Where子句来缓解。

我已经尝试重新设想代码以降低复杂性(但它有点抽象):

var data2Lookup = data2.ToLookup(x => x.Type);
var tmp1 = 
    listOfKeys
        .Select(key => 
            new {
                key, 
                subData1 = data1[key], 
                subData2 = data2Lookup[key].GroupBy(x=>x.Category)
            })
        .Select(x => 
            new{
                x.key, 
                x.subData1, 
                x.subData2, 
                subData2Lookup = x.subData2.ToLookup(y => y.Key)});
var tmp2 = 
    tmp1
        .Select(x => 
            new{
                x.key, 
                grouped = x.subData1
                            .Select(sd1 => 
                                new{
                                    Data1 = sd1, 
                                    Data2 = subData2Lookup[sd1]
                                })
            });
var result =
    tmp2
        .ToDictionary(x => x.key, x => x.grouped);

在我看来,处理在results的建筑中间有点任意,但是不应该影响它,对吗?

所以一旦构建了results,我们就来处理它......

var items = result.SelectMany(kvp => kvp.Value);
for(var item in items)
{
    item.Data1.Process(item.Data2);
}

修改

我故意避免使用并行fttb,所以如果你可以通过添加一些并行魔法来实现这一点,那么可能会有进一步的加速。

答案 1 :(得分:1)

根据您发布的百分比,您说grouped非常大,您肯定会因为内循环瘫痪而受益。

这样做很简单

   var grouped = primaryData.Select(x => new { ID = x.Key, 
                                               Data1 = subData1[x.Key],
                                               Data2 = subData2[x.Key] }).ToList();
   Parallel.ForEach(grouped, (item) => 
   {
       item.Data1.Results = new Results(item.ID, item.Data2);
       item.Data1.Status = new Status(item.ID, item.Data2);
   });

   results.Add(key, grouped);

这假设new Results(item.ID, item.Data2);new Status(item.ID, item.Data2);可以安全地同时进行多次初始化(我唯一担心的是如果他们在内部访问非线程安全的static资源,甚至所以一个非线程安全的构造函数是 真的 糟糕的设计缺陷)


有一个很大的问题:这只会在你受CPU限制的情况下有所帮助。如果ResultsStatus是IO绑定的(例如,它正在等待数据库调用或硬盘驱动器上的文件),这样做会伤害您的性能而不是帮助它。如果你是IO绑定而不是CPU绑定,唯一的选择是购买更快的硬件,尝试更多地优化这两种方法,或者如果可能的话在内存中使用缓存,这样你就不需要做慢速的IO。