SwiftProgramlamaSwift Geliştirici

**Swift**'in birden fazla yükü taşıyan **enum**'larda aktif durumları ayırt etmek için, harici ayırt edici depolama yerine yük türleri içinde kullanılmayan bitleri kullanarak uyguladığı bit düzeyi düzenleme stratejisini açıklayın.

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

Sorunun yanıtı

Sorunun tarihi

Geçmişte, sistem programlamasında ayrık birleşimler belirli etiket alanları veya varyant durumları ayırt etmek için manuel bellek düzeni gerektiriyordu. Swift, güvenli birleşimlerin eksikliğinden evrimleşti ve enum düzeni için bir derleyici yönetilen yaklaşım gerektirdi; bu, tür güvenliğini garanti ederken bellek verimliliğini maksimize etti. Swift'in erken sürümleri zaten ek varlıklar kullanarak tek yükü taşıyan enum'ları (örneğin, Optional) optimize etti, ancak birden fazla yük senaryosu, basit etiket baytı ön ekleriyle ilişkili bellek şişmesini önlemek için daha karmaşık bit düzeyinde analizi gerektiriyordu.

Sorun

Bir enum birçok durumu farklı ilişkili yük türleri ile taşıdığında (örneğin, case text(String), number(Int), data([UInt8])), derleyici, çalışma zamanı desen eşleştirme sırasında hangi durumun aktif olduğunu belirlemek için yeterli bilgiyi saklamak zorundadır. Basitçe bir ayırt edici baytı eklemek, özellikle küçük yükler için toplam boyutu önemli ölçüde artırır ve bellek ayak izi kritik olan C-tarzı birleşimlerle ABI uyumluluğunu bozarak. Zorluk, durumu ayırt edici kodlamak için kendiliğinden olan bit desenlerini (kullanılmayan bitler) yük türlerinin kendileri içinde kullanmaktır; bu, toplam tahsisat boyutunu genişletmeden gerçekleştirilmelidir.

Çözüm

Swift, önce tüm yük türleri üzerindeki kullanılmayan bit desenlerinin kesişimini (kullanılmayan bitler) hesaplayarak başlayan birden fazla yükü taşıyan enum düzenleme stratejisi uygular. Eğer yeterli kullanılmayan bit varsa—örneğin, String küçük dize optimizasyon bitlerini kullandığında veya referans türlerinin işaretçi hizalama boşlukları kullandığında—derleyici durum etiketini doğrudan bu bitler içinde saklar, en büyük yük boyutunu korur. Yük türleri mevcut kullanılmayan bitleri tükendiğinde (örneğin, hizalama boşluğu olmayan iki Int64 yükü), derleyici, açık bir ayırt edici olarak ek bir bayt (veya kelime) eklemeye geri döner, anlamlı durum tanımlamasını garanti ederken aşırı yükü minimuma indirir.

Hayattan bir durum

Sorun açıklaması

Gerçek zamanlı bir oyun istemcisi için yüksek akışlı bir ağ paket ayrıştırıcı geliştirirken, ekip ping(Int64), payload(Data) ve error(UInt8) durumları için bir Packet enum'u tanımladı. Profil oluşturma, enum'un bellek ayak izinin, paket toplu işleme sırasında önbellek thrashing'e neden olan ve 16ms çerçeve bütçesinin ötesine geçen örtük bir ayırt edici alan nedeniyle L1 önbellek satırını aştığını ortaya çıkardı.

Değerlendirilen farklı çözümler

Çözüm 1: Ham baytlarla manuel bir birleşim

Ekip, yükleri ayrı bir etiket ile bir struct içinde manuel olarak overlay etmek için bir UnsafeMutablePointer kullanmayı düşündü ve C birleşimlerine benzer bir yapı sundu. Bu yaklaşım sıfır maliyetli durum ayırt etme sağladı ancak Swift'in tür güvenliğinden vazgeçti ve manuel bellek yönetimi gerektirdi, bu da eşzamanlı ağ geri çağırmalarını işlerken kullanım sonrası serbest bırakma hataları riskini artırdı. Ayrıca, bu çözüm ARC entegrasyonunu bozdu ve referans sayımlı yükler için manuel tutma/serbest bırakma çağrıları gerektiriyordu; bu da bellek yönetimini karmaşıklaştırıyordu.

Çözüm 2: Protokol tabanlı tür silinmesi

Başka bir yaklaşım, enum'u bir Packet protokolü ile değiştirmek ve varoluşsal konteynerler (any Packet) veya jenerikler kullanmaktı. Bu, soyutlamayı korurken her paket için yığın tahsisi gerektiriyordu çünkü varoluşsal konteyner kutulama ve sanal yöntem yürütme maliyetleri getiriyordu. Performans kaybı, sıcak yolu için kabul edilemezdi, çünkü tahsis hızını iki katına çıkardı ve Swift çalışma zamanında çöp toplama baskısını artırdı.

Seçilen çözüm

Ekip, durumları yeniden sıralayarak ve kendiliğinden kullanılmayan bitlere sahip yük türlerini kullanarak Swift'in çoklu yük optimizasyonundan yararlanacak şekilde enum'u yeniden yapılandırdı. Int64'ü üst baytı ayrılmış bir özel UInt56 yapısı ile değiştirdiler ve error'ın daha büyük yükün kullanılmayan bit desenleriyle hizalanmasını sağlamak üzere UInt8 yerine UInt32 kullandıklarından emin oldular. Bu, derleyicinin durum ayırt edicisini Data ve UInt56 yüklerinin kullanılmayan bitlerine paketlemesine olanak tanıyarak, ek baytı ortadan kaldırdı ve enum boyutunu 24 bayttan 16 bite düşürdü.

Sonuç

Optimizasyon, paket ayrıştırıcının yığınları tek bir önbellek satırı içinde işlemesine olanak tanıyarak, çerçeve gecikmesini %40 oranında azaltmış ve enum'un kendisi için bellek tahsisat maliyetini ortadan kaldırmıştır. Kod, güvenli işaretçiler veya protokol tür silinmesine başvurmadan tam tür güvenliği ve desen eşleştirme yeteneklerini korumuştur.

Adayların genellikle kaçırdığı noktalar


Swift'in enum düzenleme stratejisi, başlıklardan birleşimleri içe aktarırken C ile birlikte nasıl etkileşir?

Swift, Clang başlıkları aracılığıyla bir C birleşimini içe aktardığında, türü tüm birleşim üyelerinin bir demetini içeren tek bir durumu olan bir enum olarak değerlendirir veya böyle işaretlenmişse @_NonBitwise kullanır. Ancak, Swift, içe aktarılan C birleşimlerine çoklu yük kullanılmayan bit optimizasyonunu uygulayamaz çünkü C birleşimleri Swift'in tür meta verileri ve kesin başlatma garantilerinden yoksundur. Derleyici, bir C birleşimi için herhangi bir bit deseninin geçerli olduğunu varsaymak zorundadır ve bu da durumu ayırt etmek için kullanılmayan bitlerin kullanılmasını engeller. Adaylar genellikle Swift'in C birleşim alanlarını yeniden sıraladığını veya örtük etiketler eklediğini yanlış varsayıyor; bunun yerine, Swift, C düzenini tam olarak korur ve Swift enum optimizasyon avantajlarını elde etmek için OptionSet desenleri veya manuel struct sarmalaması aracılığıyla açık yönetim gerektirir.


Dayanıklı çoklu yük enum'una yeni bir durum eklemek, derleyiciyi neden tamamen kullanılmayan bit optimizasyonundan vazgeçirmeye zorlar?

Dayanıklı modüller (kütüphane evrimi etkinleştirilmiş olarak derlenmiş) ABI istikrarını korumalıdır; bu da, enum'un düzeninin ikili uyumluluğu bozacak şekilde değişmeyeceği anlamına gelir. Eğer bir gelecek kütüphane sürümünde bir dayanıklı çoklu yük enum'una yeni bir durum eklenirse ve bu yeni yük türü mevcut son kullanılmayan biti tüketirse, derleyici, genişletilmiş durum alanını karşılamak için açık bir ayırt edici bayta geri dönmek zorunda kalır. Orijinal düzen, dayanıklı modülün meta verilerinde dondurulduğundan, derleyici mevcut yüklerden bitleri geri almaktan geri dönüş yapamaz. Adaylar, dayanıklılık sınırlarının yalnızca genel arayüzü dondurmakla kalmadığını, aynı zamanda iç bit düzeni sezgilerini de dondurduğunu sık sık atlar ve genellikle enum'ların performansla ilişkili kritik olduğunu garanti etmek için manuel @frozen nitelikleri gerektirir.


Derleyici, durumu ayırt etmek için "ek bir varlık" ile "kullanılmayan bit" kullanma kararını ne koşullarda verir ve bu, enum bellek hizalamasını nasıl etkiler?

Ek varlıklar, belirli bir tür içindeki geçersiz bit desenlerini ifade eder (örneğin, referans türlerinde nil işaretçileri veya Optional'ın yok durumu), oysa kullanılmayan bitler, çoklu yük enum'da birden fazla yük türü ile paylaşılan kullanılmayan bit desenleridir. Tek yükü taşıyan enum'lar için, derleyici diğer durumları temsil etmek üzere yükün ek varlıklarını kullanır. Çoklu yük enum'lar için derleyici, tüm yükler üzerindeki kullanılmayan bitlerin kesişimini hesaplar. Hizalama kısıtlamaları bunu karmaşıklaştırır: Eğer farklı yüklerde farklı ofsetlerde kullanılmayan bitler varsa, derleyici, ayırıcıyı tutarlı bir biçimde hizalamak için dolgu eklemek veya bir taşma etiketine başvurmak zorunda kalabilir. Adaylar genellikle bu iki kavramı karıştırır; geçersiz bitlerin tek yük senaryolarını (örneğin, Optional<T>) optimize ettiğini, oysa kullanılmayan bitlerin çoklu yük senaryolarını optimize ettiğini ve bunları karıştırmanın, en büyük yükün hizalama gereksinimlerini dikkatlice göz önünde bulundurması gerektiğini bilmezler.