Deferred execution membuat kueri terasa murah, padahal setiap iterasi menelusuri sumber dari awal. Kesalahan performa LINQ yang paling umum adalah mengeksekusi kueri yang sama berkali-kali tanpa disadari. Contoh berikut membuat biaya itu terlihat dengan sumber yang menghitung sendiri berapa kali ia diakses.
Mengukur Enumerasi Berulang #
csharp
record Produk(string Nama, string Kategori, int Harga, int Stok);
List<Produk> data = new()
{
new Produk("Kopi Gayo", "Minuman", 25000, 40),
new Produk("Teh Melati", "Minuman", 15000, 60),
new Produk("Susu UHT", "Minuman", 20000, 35)
};
int hitungAkses = 0;
IEnumerable<Produk> Sumber()
{
hitungAkses++;
foreach (Produk p in data)
{
yield return p;
}
}
var kueri = Sumber().Where(p => p.Harga > 15000);
int jumlah = kueri.Count();
string pertama = kueri.First().Nama;
Console.WriteLine($"Kueri: sumber diakses {hitungAkses} kali (jumlah {jumlah}, pertama {pertama})");
hitungAkses = 0;
List<Produk> salinan = Sumber().Where(p => p.Harga > 15000).ToList();
int jumlahSalinan = salinan.Count;
string pertamaSalinan = salinan[0].Nama;
Console.WriteLine($"Salinan: sumber diakses {hitungAkses} kali (jumlah {jumlahSalinan}, pertama {pertamaSalinan})");Output yang Dihasilkan:
text
Kueri: sumber diakses 2 kali (jumlah 2, pertama Kopi Gayo)
Salinan: sumber diakses 1 kali (jumlah 2, pertama Kopi Gayo)Mari kita bedah baris demi baris:
Sumber(): local function iterator yang menaikkanhitungAksessetiap kali sumber mulai diiterasi.kueri.Count(): enumerasi pertama; seluruh sumber ditelusuri untuk menghitung.kueri.First(): enumerasi kedua dari nol; sumber diakses ulang meskipun hasilnya sudah pernah dihitung.Sumber().Where(...).ToList(): satu-satunya enumerasi; hasilnya disalin menjadi list.salinan.Countdansalinan[0]: membaca list tidak mengakses sumber sama sekali.
Praktik Hemat Enumerasi #
- Materialisasi dengan
ToListatauToArraybila hasil kueri dipakai lebih dari satu kali. - Letakkan
Wherepaling ketat sedini mungkin agar elemen yang diperiksa operator berikutnya lebih sedikit. - Baca property
CountatauLengthpadaListdan array, bukan methodCount(). - Hindari
OrderBybila urutan tidak diperlukan; pengurutan selalu menelusuri seluruh sumber. - Pada EF Core, waspadai pola N+1 yaitu kueri di dalam loop; muat relasi dengan
Includeagar kueri tetap satu.
Satu catatan penyebut yang sering muncul: overhead tiap operator iterator memang ada, tetapi nilainya kecil untuk data berukuran wajar. Tulis kueri yang paling jelas lebih dulu, ukur dengan alat benchmark, lalu optimasi hanya pada titik yang benar-benar terbukti lambat.