Go'da stringler, arka uçtaki byte dizisine işaret eden bir gösterici ve bir uzunluk alanı içeren iki kelimelik bir başlık ile içsel olarak temsil edilen değişmez byte dizisidir. s[10:20] gibi ifadelerle bir string dilimlenirken, çalışma zamanı, gerçek byte'ları kopyalamadan orijinal destekleyici dizinin bir alt kümesine işaret eden yeni bir başlık oluşturur. Bu yapısal paylaşım, sabit zamanlı alt dizi işlemleri sağlar; ancak kapandığında, küçük bir alt dizi ana stringini aşarsa, çöp toplayıcının bakış açısına göre tüm destekleyici dizi ulaşılabilir kalır ve kullanılmayan kısımların geri kazanımını önler. strings.Clone fonksiyonu (Go 1.20'de tanıtılmıştır) veya string([]byte(substr)) aracılığıyla manuel kopyalama, yalnızca gerekli byte'ları içeren yeni bir dizi tahsis eder, ana verilere olan referansı koparır ve doğru çöp toplama sağlar.
Bir telemetri toplama hizmeti, string'lere çok megabaytlık JSON günlük gruplarını yükleyerek hata kodlarını dilimleme ile çıkardı. Mühendisler, hizmetin bellek ayak izinin toplam geçmiş günlük hacmi ile doğrusal olarak büyüdüğünü gözlemlediler; oysa sadece çıkarılan kimliklerin küçük bir kümesini önbelleğe alıyordu.
Temel neden, 16 byte'lık hata kodlarının geçici çok megabaytlık günlük stringlerinin alt dizeleri olarak uzun bir süre tutulmasıydı. Önbellek, bu alt dizeleri saatlerce tuttu; ana stringler teorik olarak kapsam dışında olsa da, alt dizi başlıkları hala onlara işaret ettiğinden destekleyici diziler devam etti.
Üç iyileştirme stratejisi değerlendirildi. İlk yaklaşım, JSON ayrıştırıcısını string'ler yerine byte dilimleri yayacak şekilde değiştirmeyi düşündü; ardından yalnızca gerekli segmentleri dönüştürdü. Ancak, bu, string türlerini bekleyen aşağı akış tüketicilerinin geniş kapsamlı bir yeniden yapılandırmasını gerektirdi ve önemli bir gerileme riski oluşturdu. İkinci seçenek, çöp toplamayı zorlamak için periyodik önbellek temizlemesi yapmaktı, ancak bu, beklenmeyen gecikme artışlarına neden oldu ve temel saklama sorununu ele almadan yalnızca semptomu gizledi. Üçüncü çözüm, çıkarım yapıldıktan hemen sonra strings.Clone uygulamasıydı; tam olarak 16 byte'lık bağımsız kopyalar oluşturdu. Bu yaklaşım, arayüzleri değiştirmeden veya operasyonel karmaşıklık eklemeden, çıkarım mantığına yerelleştirilmiş değişiklikler sağladığı için seçildi. Dağıtımdan sonraki metrikler, bellek kullanımının artık önbellek girişi sayısıyla ilişkilendirildiğini, işlenmiş günlük boyutuyla değil, bellek sızıntısının tamamen çözüldüğünü gösterdi.
Golang çalışma zamanı, yalnızca küçük bir bölüm referans alındığında neden destekleyici diziyi otomatik olarak sıkıştırmıyor ya da bölmüyor?
Go çöp toplayıcısı kompakt olmayan ve nesil olmayan bir yapıya sahiptir, hafıza tahsisi ucuz ve işaretçilerin sabit kalması ilkesi üzerinde çalışır. String başlıkları, byte dizilerine ham işaretçiler içerdiğinden, çalışma zamanı, tüm potansiyel referansları güncellemeden bu dizileri yeniden yerleştiremiyor veya kesemiyor; bu, Go'nun düşük gecikme hedefleriyle zıt olan okumalar veya dünyayı durduran aşamalar gerektiriyor. Eğer dikkate alınan bir işaretçi varsa, çöp toplayıcı tüm nesneyi canlı olarak işaretler; bu, tahsisatın %100'ü ya da %1'i aktif olarak kullanılıyor olsun, bir fark yaratmaz. Bu tasarım, bellek yoğunluğu optimizasyonundan ziyade hızlı tahsisat ve eş zamanlı toplama üzerine öncelik verir; bu da geliştirici bilincinin yapısal paylaşımını zorunlu kılar.
Kaçış analizinin, yığın tahsisini belirlerken alt dizi kopyalama işlemleriyle ilişkisi nedir?
strings.Clone veya manuel byte dönüşümü çağrıldığında, derleyicinin kaçış analizi, oluşan stringin mevcut yığın çerçevesinin ötesine akıp akmadığını inceler. Eğer alt dizi yığın tahsis edilmiş bir önbellekte depolanıyorsa, kopyalama işlemi mutlaka yığına kaçar; ancak, kritik ayrım, yeni tahsisin tam olarak alt dizi uzunluğuna göre ayarlandığıdır. Adaylar genellikle kaçış analizini alt dizi sızıntısı ile karıştırır ve başlığın yığın tahsisatının sızıntıyı önlediğini varsayar. Gerçekte, orijinal stringin arka dizi her zaman büyük strigler için yığın üzerinde bulunur (boyut eşiklerine ve string içi tamamlamaya bağlı olarak) ve yalnızca verilerin açıkça kopyalanması, ana stringin koleksiyonunu sağladığı bağımsız yönetilen bir yığın nesnesi oluşturur.
Kopyalama işleminin kaçınılması toplam sistem performansını gerçekten artırabileceği durumlar nelerdir?
Eğer ana string, alt dizeleriyle aynı ömre sahipse—örneğin, uygulamanın süresi boyunca sürekli kalan yapılandırma dosyaları okunduğunda—strings.Clone'dan kaçınmak, gereksiz tahsis ve bellek kopyalama yükünü ortadan kaldırır. Uzun süreli depolama olmaksızın, stringlerin geçici olarak işlendiği, okuma ağırlıklı senaryolar, sıfır kopyalı dilimleme ile önemli bir verim artırımı sağlar; bu, CPU önbelleklerinin sıcak kalmasını sağlarken tahsiscinin üzerindeki baskıyı azaltır. Bu optimizasyon, destekleyici dizinin tutulmasının maliyetinin (bellek), tahsis etme ve kopyalamanın (CPU) maliyetinden daha az olduğu durumlarda uygulanır; örneğin, ana ve alt stringlerin birlikte erişilemez hale geldiği kısa ömürlü istek işleyicilerde.