我无法理解GroupBy()对于多遍ResultSelector如何执行比单遍版本更快的执行速度。多通道GroupBy()如何比一次通过更快?
鉴于这一类:
public class DummyItem
{
public string Category { get; set; }
public decimal V1 { get; set; }
public decimal V2 { get; set; }
}
我创建具有100000个条目的阵列与一些随机数据,然后迭代以下查询:
方法1:多个通行证类别总量
var q = randomData.GroupBy(
x => x.Category,
(k, l) => new DummyItem
{
Category = k,
V1 = l.Sum(x => x.V1), // Iterate the items for this category
V2 = l.Sum(x => x.V2), // Iterate them again
}
);
它似乎是双处理内部枚举,其中为每个类别添加V1和V2。
所以我把下面的选择放在一起,假设这将通过一次性计算类别总数来提供更好的性能。
方法2:A类单通道总计
var q = randomData.GroupBy(
x => x.Category,
(k, l) => l.Aggregate(// Iterate the inner list once per category
new decimal[2],
(t,d) =>
{
t[0] += d.V1;
t[1] += d.V2;
return t;
},
t => new DummyItem{ Category = k, V1=t[0], V2=t[1] }
)
);
相当典型的结果:
'Multiple pass': iterations=5 average=2,961 ms each
'Single pass': iterations=5 average=5,146 ms each
令人难以置信的是,方法2最多需要两次只要方法1.我遇到了许多基准改变了V *性质的数量,不同类别的数量和其他因素。虽然性能差异的幅度变化,方法2是总是大大低于方法1。
我在这里缺少什么基本?方法1如何比方法2更快?
(我感觉到捂脸来了...)
* UPDATE *
后@尔卡的答案,我认为这将是值得去除的GroupBy()图片以查看是否按预期方式执行大型列表上的简单聚合。该任务仅仅是计算在100,000个随机行的同一列表上的两个十进制变量的总计。
结果延续了惊喜:
SUM:的ForEach
decimal t1 = 0M;
decimal t2 = 0M;
foreach(var item in randomData)
{
t1 += item.V1;
t2 += item.V2;
}
基线。我相信获得所需产出的最快方式。
SUM:多道
x = randomData.Sum(x => x.V1);
y = randomData.Sum(x => x.V2);
SUM:SINGLEPASS
var result = randomData.Aggregate(new DummyItem(), (t, x) =>
{
t.V1 += x.V1;
t.V2 += x.V2;
return t;
});
的结果如下:
'SUM: ForEach': iterations=10 average=1,793 ms each
'SUM: Multipass': iterations=10 average=2,030 ms each
'SUM: Singlepass': iterations=10 average=5,714 ms each
令人惊讶的是揭示了问题无关与GroupBy。该行为通常与数据聚合一致。我认为一次完成数据聚合更好的假设是错误的(可能是我的数据库根源导致的宿醉)。
(捂脸)
正如@Jirka指出了在衬里表面上存在的用于多遍方法中,意味着它是仅比基线“的ForEach”慢。我天真的尝试优化到单通,跑慢了近3倍!
看来,在处理内存中列表时,无论您希望对列表中的项目执行什么操作,都可能是性能上的一个更大的因素,而不是迭代开销。
感谢您分享您的其他意见。不要放弃你的直觉。单程算法确实对大约1 MB的数据具有性能优势。但是,这种优势在最内层(瓶颈)循环中发生的方法调用显得相形见绌。 – 2012-03-14 13:24:32