LINQ GroupBy'ı 2 Milyon Satırı Bellekte Tuttu

Bir raporlama endpoint'i aylar sonra çöp toplayıcı grafiklerinde ortaya çıktı. Sadece bölge başına bir tutarı toplayıp altı sayı döndürmesi gerekiyordu. Altı. Bu kadar küçük bir şey bellek basıncı yaratmamalıydı, ama ayırma sayacı başka türlü söylüyordu.

Sorunlu satır şuydu:

1var totals = rows.GroupBy(r => r.Region).ToDictionary(g => g.Key, g => g.Sum(r => (long)r.Amount));

Temiz görünüyor ve yaptığı şeyi söylüyor. Ama altı toplamı üretmek için, önce iki milyonun tamamını anahtara göre gruplandırıyor, sonra her grubu yürüyor ve topluyordu. Sonra grupları atıyor. Yani 2 milyonun tamamını bellekte tutmuş, ama sadece sonuçları kullandı.

Bu durumda LINQ Enumerable.Sum() kullanmak daha iyi bir seçim. Gruplandırma yapılmadan, her bölge için doğrudan toplayabilirsin:

1var totals = rows.GroupBy(r => r.Region).ToDictionary(g => g.Key, g => rows.Where(r => r.Region == g.Key).Sum(r => (long)r.Amount));

Ama daha verimli yol, hiç GroupBy kullanmamak. Bir Dictionary ile manuel döngü:

1var totals = new Dictionary<string, long>();
2foreach (var row in rows)
3{
4if (!totals.ContainsKey(row.Region))
5totals[row.Region] = 0;
6totals[row.Region] += (long)row.Amount;
7}

Ya da LINQ'nun aggregate yöntemlerini akıllıca kullanmak. Bu yazıda yazar, ölçüm yaparak hangi yaklaşımın 100 MB'lık tahsisi ne kadar azalttığını gösteriyor.

Ana ders: Bir LINQ sorgusu temiz yazılmışsa, mutlaka verimli değildir. Özellikle büyük veri setlerinde, intermediate collections oluşturan operasyonları (GroupBy, ToList, ToDictionary) sorguladığında ne yaptığını bilmek gerek. Profiler açıp gerçek tahsisi ölçmek, hintler ve tahminlerden bin kat daha değerli.

konu hakkında tepkinizi verebilirsiniz