Lambda yang nyaman ditulis tidak selalu bebas biaya. Bentuk lambda menentukan apa yang dialokasikan runtime: lambda tanpa capture dapat di-cache compiler sehingga hampir tanpa alokasi, sedangkan lambda yang menangkap variabel menghasilkan objek closure dan delegate baru. Bab ini mengukur perbedaannya langsung dengan alat bawaan runtime.
Dua hal yang dapat dialokasikan: objek closure, yaitu class pembungkus variabel yang ditangkap, dan objek delegate. Lambda non-capturing tidak membutuhkan closure, dan compiler menyimpan delegate-nya di cache sekali saja. Lambda capturing per-iterasi membutuhkan keduanya di setiap iterasi, dan biaya itu terlihat sebagai tekanan pada garbage collector di jalur panas.
Mengukur Alokasi Dua Bentuk Lambda #
int total = 0;
int hasil = 0;
Func<int, int> kuadrat = x => x * x;
long sebelum = GC.GetTotalAllocatedBytes(precise: true);
for (int i = 0; i < 1000; i++)
{
total += kuadrat(i);
}
long sesudah = GC.GetTotalAllocatedBytes(precise: true);
Console.WriteLine($"Lambda non-capturing: {sesudah - sebelum} byte dialokasikan");
sebelum = GC.GetTotalAllocatedBytes(precise: true);
for (int i = 0; i < 1000; i++)
{
int salin = i;
Func<int, int> tigaKali = x => x * salin;
hasil += tigaKali(i);
}
sesudah = GC.GetTotalAllocatedBytes(precise: true);
Console.WriteLine($"Lambda capturing : {sesudah - sebelum} byte dialokasikan");Output yang Dihasilkan:
Lambda non-capturing: 0 byte dialokasikan
Lambda capturing : 88000 byte dialokasikanBedah Kode Baris per Baris #
Mari kita telaah kode di atas:
- Baris 4: Lambda
kuadrattidak menangkap variabel apa pun. Compiler menyimpan delegate-nya di cache statis, sehingga satu-satunya alokasi terjadi saat deklarasi, di luar rentang pengukuran. - Baris 5 dan 11:
GC.GetTotalAllocatedBytes(precise: true)menghitung total byte yang dialokasikan sejak program berjalan, sehingga selisihnya adalah biaya persis dari loop di antaranya. - Baris 12-15: Setiap iterasi mendeklarasikan
salindan lambdatigaKali. Karena lambda menangkapsalinyang berubah tiap iterasi, compiler membuat satu closure baru dan satu delegate baru per iterasi. - Baris 17: Angka
88000berasal dari 1000 iterasi kali 88 byte, yaitu 24 byte objek closure ditambah 64 byte delegate. Selisih0melawan88000menutup argumennya.
Angka 0 dan 88000 diambil dari satu eksekusi nyata pada .NET 9; angka pasti dapat berbeda di lingkungan lain, tetapi pola nol melawan per-iterasi tetap. Ukurlah dengan method yang sama ketika membandingkan kondisi di lingkungan kita sendiri.