GoProgramlamaKıdemli Go Backend Mühendisi

**Go** derleyicisi, genel işlev örneklerini kod tekrarını en aza indirmek için hangi kriterlere göre tür argümanlarını gruplar?

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

Sorunun Cevabı

Go derleyicisi, 1.18 sürümünde tanıtılan genel tipler derlenirken GCshape stenciling adı verilen bir teknik kullanır. Tarihsel olarak, diller genel tipleri ya tam monomorfizasyon aracılığıyla—her tür örneklemesi için ayrı makine kodu üreterek ikili şişkinlik yaratarak—ya da boxlama yoluyla—türleri silerek çalışma zamanı aşırı yükü ve tahsisat maliyeti pahasına—uyguladı. Go'nun karşılaştığı sorun, ikili boyutun önemli olduğu yüksek performanslı sistem programlama desteği sağlamak, aynı zamanda tam olarak yürütme hızını feda etmemekti.

Çözüm, somut türleri boyutları ve gösterge bitmap'leri (türdeki gösterge kalıbı) ile tanımlanan GC şekli tarafından gruplamaktır. Derleyici, aynı GC şekline sahip tüm türler için tek bir işlev örneği oluşturur ve çalışma zamanı sözlüğü içindeki tür meta verilerini örtük bir parametre olarak geçirir.

// Hem *int hem de *string, aynı örneğe sahiptir // çünkü aynı GC şekline (tek gösterge) sahiptirler. func Identity[T any](x T) T { return x } func main() { Identity((*int)(nil)) // Örnekleme #1'i kullanır Identity((*string)(nil)) // Örnekleme #1'i kullanır (aynı şekil) Identity(42) // Örnekleme #2'yi kullanır (skalar, gösterge yok) }

Hayattan Bir Durum

Ekibimiz, genel ara katman yöneticileri Handler[T Event] kullanarak yüksek verimli bir olay işleme kanalı inşa ediyordu. Düşük gecikme süresini ve konteynerleştirilen dağıtım için makul ikili boyutunu korurken, elli farklı olay türünü işlemek zorundaydık.

İlk yaklaşım interface{} kullanarak tür tahminlerine dayanıyor ve çalışma zamanı tür anahtarlarını kullanıyordu. Bu, esneklik sağladı ve eski Go sürümlerinde başarılı oldu, ancak önemli bir tahsisat aşırı yükü getirdi—her arayüzde sarılmış bir olay yığın tahsisi gerektiriyordu—ve türlerin uyumsuz olduğunda üretimde paniklere yol açarak derleme zamanı tür güvenliğini ortadan kaldırıyordu.

İkinci yaklaşım, go generate kullanarak üçüncü taraf araçlarla HandlerClickEvent, HandlerPurchaseEvent vb. oluşturmayı içeren derleme zamanı kodu üretimiydi. Bu, çalışma zamanı aşırı yükü olmadan optimal performans sağladı, ancak elli olay türünü desteklediğinde ikili boyutumuzu 40MB şişirdi ve oluşturucu şablonlarını güncellerken bakım kabusları yarattı.

Üçüncü yaklaşımı seçtik: dikkatli GC şekillerine sahip yerel Go generikleri. Olay türlerimizin yapılar için gösterge olduğu konusunda emin olduk (eşit GC şekli), bu sayede derleyici örneklemeleri yeniden kullanabildi. Sadece 2MB'lik bir ikili boyut artışı karşılığında yöntem yönlendirmeleri için sözlük aramaları yapmanın küçük aşırı yükünü kabul ettik. Sonuç, interface{} kullanımıyla karşılaştırıldığında %15'lik bir gecikme azalması ve tam kod üretimiyle karşılaştırıldığında yönetilebilir bir ikili ayak izi oldu.

Adayların Sıklıkla Kaçırdığı Şeyler


Çalışma zamanı sözlüğü, paylaşılan genel örneklemelere tür spesifik bilgileri nasıl sağlar?

Sözlük, tür tanımlayıcıları (_type), yöntem tabloları (itab) ve GC meta verilerine işaret eden göstergeler içeren bir yapıdır. Derleyici func Print[T any](x T) gibi bir genel işlev için kod oluşturduğunda, sözlüğü örtük ilk argüman olarak geçirir. x.String() yöntemini çağırmak için, üretilen kod, doğrudan bir çağrı derlemek yerine sözlükte yöntem işaretçisini arar, bu sayede aynı makine kodu T=bytes.Buffer ve T=strings.Builder gibi farklı yöntem uygulamaları ile başa çıkabilir.


İki farklı gösterge türü neden ayrı örneklemeler gerektirirken, iç türleri aynı örneklemeyi paylaşabilir?

Go, türleri GCshape'e göre sınıflandırır; bu, yalnızca çöp toplayıcı ve tahsisatla ilgili bellek düzenine dikkat eder. Hem *int hem de *string, bir gösterge içeren tek bir makine kelimesinden oluşur ve aynı şekil sınıfında yer alır. Tersine, int, gösterge içermediğinden belirli bir boyuta hizalanır, ancak string bir gösterge ve bir uzunluk içeren iki kelimeden oluşan bir yapıdır. Bellek düzenleri farklı olduğu için, uygun çöp toplama ve bellek adresleme işlemlerini gerçekleştirmek için ayrı oluşturulmuş kod yollarına ihtiyaç duyarlar.


Genel sınırlamalarda değer alıcıları ile gösterge alıcıları kullanmanın performans etkisi nedir?

Bir genel işlev, bir tür parametresi T üzerinde bir yöntemi çağırdığında, derleyici, her olası T için çalışan kodu oluşturmak zorundadır. Kısıtlama bir değer alıcısı gerektiriyorsa func (T) Method(), ancak somut tür büyükse, derleyici büyük ihtimalle sözlükleri geçmek ve dolaylı çağrılar yapmak zorunda kalabilir; bu da içeri alma işlemini engelleyebilir. Gösterge alıcıları kullanan func (*T) Method(), genellikle daha iyi optimizasyon sağlamanıza olanak tanır çünkü gösterge türleri daha sık GC şekillerini paylaşır ve derleyici, somut tür derleme zamanı belirli örnekleme bağlamlarında bilindiğinde çağrıları daha kolay bir şekilde devirtme yapabilir.