SwiftProgramlamaKıdemli Swift Geliştirici

Fonksiyon nitelikleri ve görünürlük değiştiricilerinin hangi özel kombinasyonu, Swift'te kapsüllemeyi korurken sıfır maliyetli, modüller arası genel özelleştirmeyi sağlar?

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

Sorunun cevabı

@inlinable niteliği, Swift derleyicisini bir fonksiyonun uygulamasını modül arayüz dosyasına seri hale getirmesi için yönlendirir; bu, gövdenin, genel özelleştirme ve sabit katlama gibi saldırgan optimizasyonları sağlamak amacıyla, derleme zamanında istemci modüllerine doğrudan kopyalanmasına izin verir. Ancak, iç içe geçmiş kod, istemcinin derleme birimindeki tüm sembol referanslarını çözmek zorunda olduğundan, @inlinable fonksiyonu tarafından erişilen herhangi bir içsel tür, fonksiyon veya özellik @usableFromInline ile işaretlenmelidir; bu, bunların derleyiciye, genel API olarak yayımlanmadan maruz bırakılmasını sağlar.

// Dayanıklı bir çerçeve modülünde @usableFromInline internal struct InternalBuffer { @usableFromInline var storage: [Int] } @inlinable public func fastSum(_ buffer: InternalBuffer) -> Int { // @usableFromInline sayesinde içsel depolama alanına erişebilir return buffer.storage.reduce(0, +) }

Bu kombinasyon, kütüphane yazarlarının genel kodun istemci ikili dosyasında monomorfize edildiği sıfır maliyetli soyutlamalar sunmasına olanak tanır, ancak fonksiyon gövdesinin kararlı ikili arayüzün bir parçası haline gelmesi nedeniyle bazı ABI esnekliğinden feragat eder.

Hayattan bir durum

Yüksek verimli bir makine öğrenimi çerçevesi geliştiren bir ekip, istemci uygulamalara genel bir matris çarpma fonksiyonu matmul<T: Numeric> sunmak zorundaydı, ancak profil oluşturma, modüller arası fonksiyon çağrı gecikmesi ve özelleştirme eksikliğinin performansı % kırk oranında azalttığını ortaya koydu. Kütüphane, bir ikili Swift paketi olarak dağıtıldığı için, istemcilere kaynak düzeyinde optimizasyonlar sağlanamadı.

Bir yaklaşım, tüm yardımcı türleri ve uygulama fonksiyonunu genel yapmak, içsel tampon yönetimi ve adım hesaplamalarının her detayını ortaya çıkarmaktı. Bu, iç içe geçişe izin verecek olsa da, ekibin bu belirli iç türleri daima kararlı API olarak sürdürmesini zorunlu kılacak, gelecekteki yeniden yapılandırmaları engelleyecek ve tüketicilerin asla doğrudan dokunmaması gereken uygulama detaylarını genel arayüze karıştıracaktı.

Düşünülen bir diğer seçenek, aynı modül içindeki kodu saldırgan bir şekilde iç içe geçiren ancak fonksiyon gövdesini diğer modüllere dışa aktarmayan @inline(__always) kullanmaktı; bu API'yi temiz tutabilirdi ancak istemci derleyicisinin genel T'yi belirli sayısal türler için, örneğin Float16 veya Double, özelleştirmesine izin vermeyecek, çalışma zamanı geçiş gecikmesini koruyacak ve performans hedeflerine ulaşamayacaktı.

Mühendisler nihayet giriş noktasını @inlinable ile işaretledi ve içsel tampon yapıları ve aritmetik yardımcıları @usableFromInline ile nitelendirdi. Bu strateji, genel özelleştirme ve istemci çağrı noktalarında iç içe geçme için derleyiciye yeterince uygulama ayrıntısı sunarken, sembollerin genel belgelere girmesini engelledi. Sonuç olarak istemci uygulamaları, elle açılmış C koduna benzer bir performansa ulaştı, ancak çerçevenin ikili boyutu modüller arasında kod çoğalması nedeniyle biraz arttı ve ekip, fonksiyonu düzeltmenin istemcilerin yeniden derlemesini gerektireceğini kabul etti.

Adayların genellikle gözden kaçırdığı noktalar

Modüller arası sınırlar açısından @inlinable ve @inline(__always) arasındaki temel ayrım nedir?

@inlinable, fonksiyon gövdesini .swiftinterface dosyasına yazan bir modül arayüz sözleşmesidir, derleyicinin uygulamayı bağımlı modüllere derleme sırasında doğrudan iletmesine izin verir; bu, modüller arası genel özelleştirme için çok önemlidir. Aksine, @inline(__always) yalnızca yerel derleme birimi için bir optimizasyon ipucudur; optimize ediciye modül içindeki çağrı yığınını düzleştirmesini söyler ama gövdeyi harici derleyicilere erişilebilir kılmaz, bu da istemci modüllerinin yine de işlevi dayanıklı dolaylama yoluyla çağırdığı ve genel geçiş gecikmesini ortadan kaldıramadığı anlamına gelir.

Neden Swift, @inlinable fonksiyonları tarafından referans verilen içsel semboller için @usableFromInline gerektiriyor, sadece görünürlük çıkarsamak yerine?

Bir fonksiyon bir istemci modülüne iç içe geçmiş olduğunda, derleyici, çağrı noktasındaki bu kod için somut makine talimatları üretmek zorundadır; bu, her referans verilen varlık için tam tür meta verisi ve sembol adresleri gerektirir; içsel semboller, kapsüllemeyi sağlamak için modül arayüzünden kasıtlı olarak hariç tutulur. @usableFromInline, sembol tanımını arayüz dosyasında ortaya çıkaran, ancak istemci kaynak koduna erişilebilir kılmayan özel bir derleyici görünürlük seviyesidir, kod üretim gereksinimlerini karşılarken kaynak düzeyinde gizliliği korur ve kazara API sızıntısını engeller.

@inlinable kullanmak, bir Swift kütüphanesinin ABI stabilitesi ve ikili boyut özelliklerini nasıl etkiler?

Bir fonksiyonu @inlinable olarak işaretlemek, uygulamanın kütüphanenin ABI'sine gömülmesini sağlar; bu, fonksiyon gövdesinde yapılan herhangi bir değişikliğin — örneğin bir hatayı düzeltme veya bir algoritmayı geliştirme — tüm istemci modüllerinin güncellemeyi gözlemlemek için yeniden derlenmesini gerektiren yıkıcı bir ikili değişiklik oluşturması anlamına gelir, oysa dayanıklı fonksiyonlar uygulamaları bağımsız olarak değiştirilebilir. Ayrıca, derleyici, işlev gövdesini tüm istemci ikili dosyalarında her çağrı noktasında çoğaltır ve tek bir paylaşılan kütüphane adresine başvurmaktan kaçınır, bu da @inlinable'ın nihai uygulamanın toplam ikili boyutunu önemli ölçüde artırdığı anlamına gelir ve bu da sık sık çağrılan büyük yardımcı fonksiyonlar için uygunsuz hale getirir.