我无法理解GroupBy()对多传递ResultSelector的执行速度比单传传递更快。
鉴于此课程:
public class DummyItem
{
public string Category { get; set; }
public decimal V1 { get; set; }
public decimal V2 { get; set; }
}
我使用一些随机数据创建一个包含100,000个条目的数组,然后迭代以下查询:
方法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:类别总计的单次传递
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更快?
(我感觉到了一个面孔......)
*更新 *
在@Jirka的回答之后,我认为值得从图片中删除GroupBy()以查看大型列表上的简单聚合是否按预期执行。任务只是计算同一个100,000个随机行列表中两个十进制变量的总数。
结果延续了惊喜:
SUM:ForEach
decimal t1 = 0M;
decimal t2 = 0M;
foreach(var item in randomData)
{
t1 += item.V1;
t2 += item.V2;
}
基线。我相信获得所需输出的最快方法。
SUM:Multipass
x = randomData.Sum(x => x.V1);
y = randomData.Sum(x => x.V2);
SUM:单通道
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所指出的那样,多通道方法显然存在内陷,意味着它只比基线慢了一点,而且每个方法都有。我天真地尝试优化到单程,跑得差不多3倍!
在处理内存列表时,无论你希望对列表中的项目做什么,它的性能都可能远远超过迭代开销。
答案 0 :(得分:1)
聚合必须在此过程中创建99,999个激活记录(对于非可内联方法调用)。这抵消了单程的优势。
将Count,Sum,Average等视为Aggregate在一般情况下可以做的优化特例。