Go Runtime'ın Elden Alacağı Bir Fincan Yeşil Çayın Huzuru, GreenTea GC
Neden Yeni Bir GC Ortaya Çıkıyor?
Mevcut GC Neden?
Mevcut Go GC'si aşağıdaki ifadelerle açıklanabilir:
- concurrent tri-color mark and sweep
- Nesnelerin durumunu takip etmek için toplam 3 renk (beyaz, gri, siyah) kullanılır.
- Beyaz: Henüz ziyaret edilmemiş nesneler
- Gri: Ziyaret edilmiş ancak tüm alt nesneleri ziyaret edilmemiş nesneler
- Siyah: Tüm alt nesneleri ziyaret edilmiş nesneler
- Tarama bittikten sonra beyaz nesnelerin hepsi toplanır.
- GC sırasında yeni oluşturulan belleği korumak için Write Barrier kullanılır.
- Write Barrier'ı Açma/Kapama süresi, yaygın olarak bilinen Stop-The-World (bundan sonra STW) süresinin önemli bir kısmını oluşturur.
- Bu süreç eşzamanlı olarak gerçekleştirilir.
- Nesnelerin durumunu takip etmek için toplam 3 renk (beyaz, gri, siyah) kullanılır.
- No Compaction
- Go'nun GC'si sıkıştırma yapmaz.
- Bellek parçalanması meydana gelebilir.
- Ancak 32kb'den küçük nesneler, Go dilinin bellek ayırıcısının (Allocator) per-P önbelleği kullanılarak parçalanmayı en aza indirir.
- Non-Generational
- Go'nun GC'si nesneleri nesil bazında yönetmez.
- Tüm nesneler aynı nesile aittir.
- Escape Analysis
- Go, Escape Analysis aracılığıyla nesnenin yığına mı (stack) yoksa yığına (heap) mı tahsis edileceğini belirler.
- Kabaca, dangling pointer veya arayüz kullanılıyorsa yığına tahsis edildiği düşünülebilir.
Önemli Olan
Go'nun GC'sinin tüm nesneleri kökten başlayarak keşfettiği ve üç renkli işaretleme yaptığı gerçeğidir. Bu süreci tek bir cümlede ifade edersek, "topolojik sıralama grafiği eşzamanlı keşif algoritması" diyebiliriz. Ancak her nesnenin farklı bellek bölgelerinde bulunma olasılığı yüksektir. Açıkça söylemek gerekirse,
- Birbirinden yaklaşık 32MB uzaklıkta iki bellek bölgesi olduğunu varsayalım.
- Bu iki bellek bölgesinde her birine 5 nesne tahsis edilmiştir.
- Nesne A, B, C 1. bölgede
- Nesne D, E 2. bölgede
- Nesnelerin referans sırası
A -> D -> B -> E -> C
şeklindedir. - GC başladığında, A'yı ziyaret eder, sonra D'yi, sonra B'yi, sonra E'yi ve sonra C'yi ziyaret eder.
- Bu sırada, A ve D farklı bellek bölgelerinde bulunduğundan, A'yı ziyaret edip D'yi ziyaret etme sürecinde bellek bölgeleri arasında geçiş yapmak gerekir.
- Bu süreçte, bellek bölgeleri arasında geçiş yapmak ve yeni önbellek satırlarını doldurmak gibi, sözde bellek sıçrama maliyeti oluşur. Uygulamanın kullandığı CPU süresinin önemli bir kısmı bu GC görevine ayrılabilir.
- Bu maliyet, GC devam ettiği sürece sürekli olarak ortaya çıkar.
Peki Sorun Ne?
GC'nin bu davranışı aşağıdaki durumlarda sorunludur:
- Çok sayıda çekirdek ve büyük bellek olması durumunda
- Go'nun GC'si temel olarak eşzamanlı olarak gerçekleştirilir.
- Ancak bellek alanı geniş ve nesneler dağılmışsa, birden fazla çekirdek eşzamanlı olarak farklı bellek alanlarında nesneleri keşfeder.
- Bu süreçte CPU ve bellek arasındaki veri yolu yeterince büyük değilse, bellek bant genişliği bir darboğaz haline gelebilir.
- Ayrıca fiziksel olarak uzaklık olduğundan, her bir keşif nispeten büyük bir gecikmeye neden olacaktır.
- Çok sayıda küçük nesne olması durumunda
- Eğer derinliği fazla veya çok sayıda çocuğu olan bir ağaç veya grafiği Go ile işletiyorsanız, GC'nin bu nesnelerin hepsini keşfetmesi gerekir.
- Bu süreçte nesneler farklı bellek bölgelerine dağılmışsa, ilk nedenden dolayı gecikme yaşanır.
- Ayrıca, çok sayıda küçük nesne varsa, GC'nin bu nesnelerin hepsini keşfetmesi gerektiğinden, GC yürütülürken CPU çekirdekleri önemli ölçüde kapasiteyi GC'ye ayıracak ve çalışacaklardır.
Bu sorunları çözmek için Golang ekibi GreenTea GC'yi duyurdu.
Daha Fazla Yeşil Çay İçme Lüksünüz Yok
Yeşil Çayı Ne Elinizden Alıyor?
Mevcut GC'nin en hızlı çözümünü uygulayabilecek alan olarak bellek yerelliği görülmektedir. Yani, nesnelerin birbirine yakın konumlanmasını sağlayarak bellek sıçramalarını en aza indirmektir. Ancak kod yazan programcının kalıpları zorlanamaz ve iş akışına göre nesne tahsisi tahmin edilemez.
Bu nedenle Golang ekibinin seçtiği yöntem Bellek Aralığı (Memory Span) olmuştur.
Bellek Aralığı Nedir?
Bellek aralığı, nispeten büyük bir bellek alanı tahsis edilerek küçük nesnelerin tahsis edildiği yerdir. Ve GreenTea GC adı verilen yeni GC, bu bellek aralığı üzerinde çöp toplama işlemini gerçekleştirir. Detaylı çalışma, mevcut Tri-Color Mark and Sweep ile neredeyse aynıdır.
GreenTea GC'nin Çalışması
Öncelikle GreenTea GC bir bellek aralığı tahsis eder. Boyutu daha önce belirtildiği gibi, belirli bir büyüklüğe sahip olan 8 KiB'dir. Ve bunun içine maksimum 512 bayt boyutunda nesneler tahsis edilebilir. Bu, örnek olarak verilen ağaç veya grafikteki düğüm boyutu veya genel yığına kaçan yapıların boyutunun bundan daha büyük olmasının zor olduğu bir boyuttur. 512 bayttan küçük nesneler, yığına her kaçtıklarında bu bellek aralığına yığılır ve bu bellek aralığı dolduğunda yeni bir bellek aralığı tahsis edilir.
GC meydana geldiğinde, GreenTea GC bu bellek aralıklarını bir kuyruğa yığar ve sırayla inceler. Bu süreçte GreenTea GC, mevcut GMP modeline neredeyse benzer bir zamanlama kullanır. İş hırsızlığı gibi alanlar da uygulanmıştır. Her neyse, kuyruktan bir bellek aralığı alan işçi, kendisine tahsis edilen bellek aralığının içindeki nesneleri inceler. Bu süreçte Tri-Color Mark and Sweep aynı şekilde kullanılır.
Bunun Ne Gibi Bir Avantajı Var?
Bu süreçte mevcut GC ile temel fark sadece bir tanedir. GC'nin bir seferde gerçekleştirdiği birim, nesne'den bellek aralığı'na dönüşmüştür. Bu sayede GC aşağıdaki avantajlara sahip olur:
- Bellek Yerelliği: Nesneler bellek aralığında toplandığı için, nesneleri keşfederken bellek sıçramaları en aza indirilir. Yani, CPU önbelleği maksimum düzeyde kullanılabilir.
- GC Performans Artışı: Bellek aralığı bazında GC gerçekleştirilmesiyle, GC çok çekirdekli ortamlarda daha verimli çalışır.
- Bellek Tahsis Optimizasyonu: Bellek aralıkları sabit boyutlu olarak tahsis edildiğinden, küçük nesneleri tahsis ederken bellek tahsisinin ek yükü azalır ve parçalanma olasılığı düşer. Bu, bellek tahsisini serbest bırakma verimliliğini artırır.
Kısaca, artık 512 bayttan küçük nesneleri daha sık ve daha rahat bir şekilde tahsis edebiliriz.
Ancak burada da belirli avantajlı durumlar bulunmaktadır.
- Ağaç/Grafik Yapıları: fan-out'un yüksek olduğu ve kolay değişimin olmadığı durumlar
- B+ ağacı, Trie, AST (Abstract Syntax Tree)
- Önbellek dostu veri yapıları
- Toplu İşleme: Büyük miktarda küçük veri tahsis iş akışı
- JSON ayrıştırma ile oluşturulan çok sayıda küçük nesne
- Veritabanı sonuç kümelerindeki DAO nesneleri
Bu gibi durumlarda GreenTea GC, mevcut GC'den daha iyi performans gösterebilir. Ancak her durumda geçerli değildir ve özellikle nesnelerin farklı bellek bölgelerine dağılmış olması durumunda hala dramatik bir performans artışı beklemek zordur.
Sonuç
Golang ekibi, uzun vadede GC'yi sürekli olarak geliştirmeyi düşünüyor gibi görünüyor. Bu GreenTea GC, küçük bir alanda yapılan ikincil bir değişiklik olarak görülebilir ve mevcut GC'nin yerini almamaktadır. Ancak GreenTea GC, Golang ekibinin karşılaştığı veya beklediği sorunları görmemizi sağlayan ilginç bir örnek olarak duruyor. Karmaşık bir sorun olmasına rağmen, nispeten basit bir kavram eklenerek çözülmeye çalışılması da etkileyiciydi. Şahsen ilginç bir örnek olduğunu düşünüyorum.
GreenTea GC, Go 1.25'ten itibaren tanıtılan deneysel bir özelliktir. GOEXPERIMENT=greenteagc
ortam değişkeni kullanılarak etkinleştirilebilir ve kullanılabilir. Bu özellik henüz deneysel olduğundan, üretim ortamında kullanmadan önce yeterli test yapılması gerekmektedir.