我正在努力改善我正在做的一些数据处理的运行时间。数据最初是各种集合(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,因此无法访问async
或await
)
答案 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限制的情况下有所帮助。如果Results
或Status
是IO绑定的(例如,它正在等待数据库调用或硬盘驱动器上的文件),这样做会伤害您的性能而不是帮助它。如果你是IO绑定而不是CPU绑定,唯一的选择是购买更快的硬件,尝试更多地优化这两种方法,或者如果可能的话在内存中使用缓存,这样你就不需要做慢速的IO。