我無法理解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