SwiftProgramlamaSwift Geliştirici

Swift'in Optional türü, referans türlerini saran `none` durumunu ek depolama olmadan nasıl temsil eder ve bu mekanizma, birden fazla yük taşıyan durumu olan enum'lara nasıl genişletilir?

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

Soruya Cevap

Swift, Optional'ın none durumunun depolama aşırılığını ortadan kaldırmak için fazladan saklayıcı kullanımı (veya yedek bit paketleme) olarak bilinen bir derleyici optimizasyonu kullanır. Referans türleri için (sınıflar, kapamalar, AnyObject), temel işaretçi temsili geçersiz bir nesne referansı olmayan bir null adres (0x0) içerir; Swift, bu null işaretçiyi Optional.none'ı temsil etmek için yeniden kullanır, aynı zamanda tüm null olmayan işaretçiler Optional.some'ı temsil eder. Bu mekanizma, birden fazla yük taşıyan durumu olan genel enum'lar için genişletildiğinde, derleyici, ilişkilendirilmiş tüm değer türlerinin bit desenlerini analiz ederek ortak kullanılmayan değerleri (yedek bitler) belirler. Eğer tüm yük türleri, durum sayısını kodlamak için yeterli yedek bit paylaşıyorsa, enum bu bitler içinde durum ayırıcıyı saklar; aksi takdirde, ayrı bir etiket baytı veya kelime ekler.

Hayattan Bir Durum

Gerçek zamanlı 3B render motoru için sahne grafiği tasarlarken, ekip 2 milyon sahne düğümü için isteğe bağlı ana referanslarını saklamak zorunda kaldı. Her düğüm bir sınıf örneğiydi ve hiyerarşi, ana düğümleri temsil etmek için Optional<Node>'a ihtiyaç duyuyordu (ana yok).

Çözüm A: Paralel boolean dizisi.
Ekip, ana varlığını belirtmek için ContiguousArray<Bool> ile birlikte ContiguousArray<Node>'yi ayrı tutmayı düşündü.
Artıları: Açık kontrol, dil bağımsız kalıp.
Eksileri: Cache locality iki ayrı bellek bölgesine erişimle bozulur; bellek aşırılığı 2MB arttı (bool başına 1 byte, hizalama için doldurulmuş); ağacı yeniden yapılandırırken senkronizasyon karmaşıklığı.

Çözüm B: Gözcü düğüm kalıbı.
Mevcut olmayan anahtarları temsil etmek için küresel bir singleton "null düğüm" örneği kullanmak.
Artıları: Tek işaretçi depolama, isteğe bağlı aşırılık yok.
Eksileri: Tür güvenliği ihlal edilir; derleyici, gözcü üzerinde yanlışlıkla işlemleri engelleyemez; kod tabanında savunmacı kontroller gerektirir; gözcü gerçek düğümlere geri referans tutuyorsa referans döngüleri oluşturur.

Çözüm C: Yerel Swift Optional.
Düğüm yapısında doğrudan Optional<Node>'yı benimsemek.
Artıları: Tam derleme zamanı güvenliği, doğala uygun Swift sözdizimi, Optional'ın none için null işaretçi temsilini kullandığı için sıfır bellek aşırılığı.
Eksileri: Bu optimizasyonun spesifik olarak referans türlerine uygulandığını anlamak gerekir; Int gibi değer türleri hizalama için doldurma gerektirir.

Ekip Çözüm C'yi seçti. Çünkü Node bir sınıf olduğundan, Optional sarmalayıcısı örneğin boyutuna hiç byte eklemedi. Sonuç olarak, paralel boolean yaklaşımına kıyasla yaklaşık 16MB'lık bir bellek azaltımı elde edildi (hem boolean depolamasını hem de ilgili hizalama doldurmasını ortadan kaldırarak) ve sonraki yeniden yapılandırmalar sırasında null-dereference çökme olasılığını ortadan kaldıran derleme zamanı garantileri kazanıldı.

Adayların Sıkça Gözden Kaçırdığı

Neden Optional<Int> genellikle Int'ten daha fazla bellek kaplarken, Optional<AnyObject> AnyObject ile aynı alanı kaplar?

Int, sayısal aralığını temsil etmek için (−2^63'ten 2^63−1'e kadar) her olası bit desenini kullanan 64 bitlik iki'nin tamamı tam sayısıdır, bu da Optional ayrımcı için kullanılabilir geçersiz bit desenleri (fazladan sakinler) bırakmaz. Sonuç olarak, derleyici, isteğe bağlı durumun some veya none olup olmadığını saklamak için ayrı bir bayt (veya hizalama nedeniyle kelime) eklemek zorundadır. Öte yandan, AnyObject (ve tüm sınıf referansları), tüm sıfır bit deseninin (null) nesne adresi olarak geçersiz olduğundan, Optional, none durumu için bu null temsilini talep eder, sıfır ek depolama gerektirir.

Optional<Optional<T>>'de "yokluk" için kaç farklı makine seviyesinde temsil vardır ve bu neden eşitlik için önemlidir?

İki farklı temsil vardır: dış .none (dış seviyedeki bir null işaretçi) ve .some(.none) (iç null'a işaret eden geçerli bir dış işaretçi). İç Optional zaten kendi boşluğunu temsil etmek için null işaretçi değerini kullandığından, dış Optional, yalnızca işaretçi değeri kullanarak kendi none durumunu .some içeren bir none durumundan ayıramaz. Bu nedenle, dış katmanın ayrı bir etiket bitine ihtiyacı vardır ve iki kavramsal "nil" durumu eşit değildir (Optional(Optional.none) != Optional.none). Bu ayrım, özel API'lerden veya JSON çözümlemesinden dönen iç içe istekliler söz konusu olduğunda önemlidir; kaybolmuş anahtarlar dış null'lar üretirken, null değerler iç null'lar üretir.

Birden fazla yük taşıyan durumları olan bir enum tanımlarken, case integer(Int), case boolean(Bool) gibi, derleyicinin ayrı bir etiket baytı depolayıp depolamayacağını veya yük içinde durum ayırıcıyı yerleştirip yerleştirmeyeceğini ne belirler?

Derleyici, ilişkilendirilmiş değer türleri üzerinde yedek bit analizi yapar. Bool yalnızca en az anlamlı bit kullanır, 7 bit bırakarak. Eğer tüm durumların yükleri, her durumu benzersiz olarak tanımlamak için yeterli yedek bit sağlıyorsa (örneğin, null fazladan saklayıcısını paylaşan birden fazla sınıf referansı), enum durumu dizinini bu kullanılmayan bitler içinde paketleyebilir. Ancak, Int ve Bool, ayrı yedek bit desenlerine sahip olduklarından (Int'in yok), derleyici, integer ile boolean'ı ayırt etmek için ayrı bir etiket baytı (veya kelime) tahsis etmek zorundadır, bu da enum'un boyutunu maksimum yük boyutunun ötesine artırır.