C# String Değişkenlerinin Bellekteki Yaşamı
4 Byte'lık Büyük Yalan

Bu makale "1 Milyar Satırlık Veriyi Nasıl İşlersin?" eğitiminde kullanıcılara sunulan makalelerden bir tanesidir.

Yazılım geliştiriciler olarak günlük işlerimizde string tipini fütursuzca kullanırız. Yüksek seviyeli dillerin sağladığı konforla, bir string değişkenine değer atarız, birleştiririz (+ operatörü), parçalarız veya Replace der geçeriz.

Konsept olarak basit görünse de, arka plandaki işleyişini anlamadan yazılan kod; büyük veri setleriyle çalışırken (örneğin 1 milyar satırlık devasa metin dosyalarında) uygulamanızı bir bellek canavarına dönüştürebilir.

Gelin, konfor alanımızdan çıkıp silikonun o acımasız gerçekliğine inelim. C#'ta o çok sevdiğimiz string bellekte tam olarak nasıl duruyor?

Karakter Verisi: UTF-16

Masum bir varsayımla başlayalım: "C# ile ABCD gibi 4 karakterden oluşan bir metin, bellekte 4 bayt yer kaplar."

Bu, kocaman bir yalandır. C# (ve .NET Runtime), dünya üzerindeki tüm alfabeleri (Türkçe, Japonca, Emojiler vb.) desteklemek için varsayılan olarak UTF-16 karakter kodlamasını kullanır. Yani C#'ta her bir char, 1 bayt değil, tam 2 bayt genişliğindedir.

Sadece işin veri kısmına bakarsak; "ABCD" metni fiziksel olarak 4 karakter x 2 bayt = 8 bayt yer kaplar. Veritabanınızda veya diskinizde UTF-8 (1 bayt) olarak duran o 13 GB'lık devasa log dosyasını C# ile doğrudan belleğe çekmeye kalktığınızda, RAM tüketiminizin neden anında 26 GB'a fırladığını bu basit matematik açıklar.

Anatomik İnceleme: Bir String'in Bellek Haritası

İş sadece 2 baytlık karakterlerle bitmiyor. C#'ta string'ler birer nesnedir (System.String referans tipi) ve belleğin Managed Heap bölgesinde yaşarlar. Her nesne gibi onların da bir "kimlik kartı" (overhead) olmak zorundadır.

Gelin "ABCD" metninin 64-bit (x64) bir işlemci mimarisinde belleğe byte-byte nasıl dizildiğini bir ASCII haritası ile mikroskop altına alalım;

Gördüğünüz gibi, sadece 8 baytlık bir "ABCD" verisini tutmak için .NET, nesnenin etrafına tam 22 baytlık bir bürokrasi (overhead) ördü. Ama durun, hikaye burada bitmiyor!

Memory Alignment (Bellek Hizalaması) ve Padding

İşlemciler (CPU) belleği bayt bayt okumayı sevmezler. 64-bit bir işlemci, belleği 8 baytlık "kelimeler" halinde, yani 8'in katları şeklinde okuduğunda maksimum hıza ulaşır. Eğer veriniz 8'in katı olmayan bir adrese denk gelirse, işlemci o veriyi okumak için fazladan mesai harcar (Buna Unaligned Memory Access denir ve performansı baltalar).

Bunu bilen .NET CLR (Common Language Runtime), nesneleri Heap'e yerleştirirken Memory Alignment (Bellek Hizalaması) yapar.


Yukarıdaki "ABCD" örneğimizin toplam ham boyutu 30 bayt çıkmıştı. 30, 8'in katı değildir. CLR, işlemci yorulmasın diye bu nesnenin sonuna boş, hiçbir işe yaramayan 2 baytlık bir Padding (Doldurma) ekler.

Sonuç: 4 karakterlik masum "ABCD" metnimiz, RAM'inizde tam olarak 32 Bayt fiziksel alan işgal eder! (4 bayt nerede, 32 bayt nerede?)

Değiştirilemezlik (Immutability) Maliyeti

Bu devasa bellek israfını katlayan son bir kural vardır: C#'ta string'ler Immutable'dır (Değiştirilemez). Aşağıdaki çok sık yapılan hataya bakalım;

Siz s + "B" dediğinizde, .NET gidip mevcut "A" nesnesinin sonuna bir "B" eklemez (çünkü stringler değiştirilemez ve hizalanmış bellek blokları uzatılamaz). Bunun yerine, Heap üzerinde yepyeni bir "AB" nesnesi oluşturur. Bir sonraki döngüde yepyeni bir "ABB" nesnesi...
Her seferinde baştan Object Header'lar, uzunluklar, Null Terminator'lar ve Padding'ler yaratılır. Döngü bittiğinde, Heap üzerinde çöp haline gelmiş (Abandoned) 99 tane string nesnesi bırakırsınız. Ve Garbage Collector (GC), bu enkazı temizlemek için tüm uygulamanızı dondurup (Stop-the-world) sistemi süpürmek zorunda kalır.

Çözüm Ne?
Bu yüzden performans kritik uygulamalarda (örneğin eğitimimizdeki 13 GB'lık dosyayı okurken) sürekli yeni string'ler oluşturmak, kendi topuğumuza sıkmaktır.

Bunun yerine karakterleri string'e çevirmeden doğrudan bellekteki fiziksel adresleriyle (ReadOnlySpan<byte>, Memory<T>) veya değiştirilebilir bloklarla (StringBuilder) çalışmayı öğrenmeliyiz. İşlemcinin dilinden anlamak, tam olarak budur.

Created with