Kita mengukur alokasi pada bab Object Allocation dan melihat tekanannya pada bab Memory Pressure; kini saatnya menekannya. Pengurangan alokasi (allocation reduction) memberi dua keuntungan sekaligus: GC generasi 0 jarang penuh sehingga jeda GC berkurang, dan cache prosesor bekerja lebih baik karena data tidak terus-menerus ditulis ke lokasi baru. Teknik yang paling sering dipakai adalah mengganti penyambungan string dan memakai span untuk membaca data.

Contoh: Penyambungan String versus StringBuilder #

csharp
long sebelum = GC.GetAllocatedBytesForCurrentThread();
string teks = "";
for (int i = 0; i < 200; i++)
    teks += "ab";
long sesudah = GC.GetAllocatedBytesForCurrentThread();
Console.WriteLine($"Penyambungan string : {sesudah - sebelum} byte");

sebelum = GC.GetAllocatedBytesForCurrentThread();
var sb = new System.Text.StringBuilder();
for (int i = 0; i < 200; i++)
    sb.Append("ab");
sesudah = GC.GetAllocatedBytesForCurrentThread();
Console.WriteLine($"StringBuilder       : {sesudah - sebelum} byte");

bool cocok = System.MemoryExtensions.StartsWith(teks.AsSpan(), "ababababab".AsSpan());
Console.WriteLine($"Pemeriksaan span    : {cocok}");
Console.WriteLine($"Panjang hasil akhir : {teks.Length} karakter");

Output yang Dihasilkan:

text
Penyambungan string : 85568 byte
StringBuilder       : 1456 byte
Pemeriksaan span    : True
Panjang hasil akhir : 400 karakter

Mari kita bedah baris demi baris:

  1. teks += "ab" di dalam loop: string di .NET tidak dapat diubah (immutable), sehingga setiap iterasi membuat string baru yang menyalin seluruh isi string sebelumnya. Totalnya 85.568 byte untuk hasil akhir hanya 400 karakter.
  2. sb.Append("ab"): StringBuilder memelihara buffer internal yang diperbesar bertahap. Selisihnya hanya 1.456 byte, sekitar 60 kali lebih hemat.
  3. teks.AsSpan() dan MemoryExtensions.StartsWith: span memberi jendela baca ke string tanpa menyalin apa pun, sehingga pemeriksaan awalan tidak mengalokasikan objek baru.
  4. teks.Length: hasil akhir kedua pendekatan identik; perbedaannya murni pada jumlah byte yang dialokasikan.

Daftar Teknik yang Lazim #

  • Ganti penyambungan string berulang dengan StringBuilder atau interpolasi satu kali.
  • Pakai ReadOnlySpan<char> dan MemoryExtensions untuk memotong, mencari, dan membandingkan teks.
  • Sewa buffer besar dari ArrayPool<T> dan kembalikan setelah dipakai.
  • Simpan hasil perhitungan (caching) alih-alih menghitung ulang objek yang sama.
  • Hindari boxing dengan API generik atau tipe eksplisit.

Semua teknik di atas berangkat dari prinsip yang sama: objek yang tidak pernah dibuat tidak perlu diklaim. Prioritaskan loop panas dan jalur pemrosesan berulang, karena alokasi di sana berlipat ribuan kali per detik dan menjadi penyumbang terbesar tekanan GC.