GoProgramlamaKıdemli Go Geliştirici

**Go**'nun yazma engeli, bir goroutine bir siyah nesneye beyaz nesneye işaret eden bir işaretci yazdığında erişilebilir nesnelerin kaybolmasını nasıl önler?

Hintsage yapay zeka asistanı ile mülakatları geçin

Sorunun cevabı.

Go, nesnelerin beyazdan (işaretlenmemiş) griye (kuyruğa alınmış) ve siyaha (tamamen taranmış) geçiş yaptığı üç renkli eş zamanlı çöp toplayıcı kullanır. İşaretleme sırasında temel invariyant, siyah nesnelerin asla beyaz nesnelere işaret eden işaretçiler içermemesi gerektiğidir; çünkü bu durum toplayıcının erişilebilir belleği yanlışlıkla serbest bırakmasına yol açabilir. Bunu duraksamadan uygulamak için Go, yığına her işaretçi yazıldığında tetiklenen bir derleyici eklenmiş kanca (yazma engeli) kullanır. Bir mutatör goroutine bir işaretçi yazma işlemi gerçekleştirdiğinde, engel hedef nesnenin beyaz olup olmadığını kontrol eder; eğer öyleyse, yazmayı tamamlamadan önce hedefi hemen gri yapar ve invariyantı atomik olarak korur.

Hayattan bir durum

Gerçek zamanlı bir analiz boru hattında saniyede milyonlarca olayı işleyerek ciddi gecikmeler gözlemledik. Sistem, akış verisine dayalı olarak çocuk düğümlere referansları sık sık güncelleyerek karmaşık bir grafik yapısı kullanıyordu ve Go'nun GC döngüleri sırasında büyük işaretçi kaymaları meydana geliyordu.

İlk çözüm: Toplamaları ertelemek için GOGC'yi %200'e çıkarmayı denedik. Artılar: GC döngülerinin sıklığını azalttı, zamanla barrier yürütme sayısını düşürdü. Eksiler: Bu, maksimum yığın boyutunu dramatik şekilde artırdı ve bellek kısıtlı konteynerlerimizde OOM çöküşlarına riske attı, yalnızca gecikme zirvelerini ertelemekle kalmayıp, çözmedi.

İkinci çözüm: Düğüm yapılarını yeniden kullanmak için sync.Pool kullanan nesne havuzlama ile denemeler yaptık ve ayrıştırmaları azalttık. Artılar: Yeni beyaz nesnelerin oluşturulma oranını ve ayrıştırma baskısını azalttı. Eksiler: Yazma engelinin ek maliyeti yüksek kalmaya devam etti çünkü var olan (genellikle zaten taranmış) siyah nesnelerin içinde işaretçileri aynı hızda değiştirmeye devam ediyorduk; havuzlama, işaretçi güncellemeleri sırasında barrier yürütme maliyetini ele almadı.

Üçüncü çözüm: Grafiği, düğüm ilişkileri için doğrudan işaretçiler yerine büyük bir dilime tamsayı dizinleri kullanacak şekilde yeniden yapılandırdık. Artılar: Tam sayılı atamalar işaretçi yazımları değildir, yazma engeli mekanizmasını tamamen bypass eder ve işaretleme sırasında ilgili CPU maliyetini ortadan kaldırır. Eksiler: Deliğin ve yoğunlaştırmanın yönetimi için dilim için elle bellek yönetimi uygulamak gerekti ve kodu daha az idyomatik ve bakımını zor hale getirdi.

Seçilen çözüm: Yüksek dönüşüm oranına sahip ana graf için dizin tabanlı yaklaşımı benimsedik, statik meta veriler için işaretçileri koruduk. Bu, doğrudan yazma engeli sıcak yolunu ortadan kaldırdı, grafik bağlantı anlamlarını korudu.

Sonuç: GC sırasında gecikme %90 düştü, 15 ms’den 1.5 ms’ye, genel verimlilik ise GC yardımcı işlerin mutatörlerden CPU çalma oranının düşmesiyle %40 arttı.

Adayların sıklıkla atlattığı noktalar

Yazma engeli, işaret edilen nesneyi neden değiştirme nesnesinin işaretlenmesinin yanı sıra gölgeliyor?

Adaylar sıklıkla engelin kaynak nesnesini (yazılan nesneyi) yeniden taranması gereken nesne olarak işaretlemesi gerektiğini yanlış varsayıyor. Ancak kaynak zaten ya gri ya da siyahtır; eğer siyahtırsa, tekrar taranması pahalı olmalı ve tüm dış işaretçilerini takip etmeyi gerektirir. Buna karşılık, hedefi (yeni işaretçi değeri) hemen gri renge gölgelemenin tri-renk invariyantını sağlaması yeterlidir: eğer kaynak siyah ve hedef beyaz ise, kenar siyah-griye dönüşür, bu güvenlidir. Bu ayrım çok önemlidir çünkü iş yükünü en aza indirir (yalnızca yeni hedef kuyruklanır) ve potansiyel olarak büyük kaynak nesnelerin yeniden taranmasını gerektirmez.

Yazma engeli yığma atamalarla nasıl etkileşir ve neden yığınların yeniden taranması gerekebilir?

Yazma engelleri esasen yığın işaretçi yazımlarını kesebilse de, Go yığınlardan yığına işaretçilerle de başa çıkma zorundadır. Eğer bir goroutine, siyah bir yığın çerçevesine beyaz bir yığın nesnesine işaret eden bir işaretçi yazarsa, yazma engeli hedefi gölgelendirmek için yürütülür. Ancak, yığınlar büyüyebildiği, küçülebildiği ve kopyalanabildiği için, her yığın slotu için kesin siyah/beyaz durumlarının korunması karmaşıktır. Go, bir yığın işaretleme sırasında aktif olduysa işaretleme fazının sonunda yeniden taranması gereken kökler olarak yığınları ele alarak bunu çözer. Adaylar genellikle yığın yeniden taramasının, eş zamanlı yürütme nedeniyle yığınlarda yazma engellerinin invariyantı garanti edemediği durumlarda gerekli bir geri dönüş olduğunu gözden kaçırır ve bu son durdurma aşaması genellikle kısa ama doğruluk için önemlidir.

Dijkstra yazma engeli ile Yuasa yazma engeli arasındaki fark nedir ve hangisini kullanır?

Dijkstra engeli, bir işaretçi kurulduğunda hedef nesneyi gölgelendirir (siyah mutatör, beyaz hedef) ve siyah-beyaz kenarının asla oluşmasını engeller. Öte yandan, Yuasa engeli, üzerine yazılan eski işaretçi değerini kaydeder ve onu gölgelendirir, "başlangıçtaki anlık görüntü" özelliğini korur. Go, daha basit olduğu ve güçlü tri-renk invariyantını derhal sağladığı için hibrit Dijkstra engelini kullanır, ancak beyaz bir nesne hemen gölgelendikten sonra erişilemez hale gelirse yüzeysel çöp yaratabilir. Adaylar genellikle bunları karıştırır veya Go'nun muhafazakar yığın yönetimi nedeniyle Yuasa kullandığını varsayar; ancak Dijkstra seçiminden anlamak, Go'nun engelinin yazma ile senkronize olduğunu açıklamaktadır.