Cepat di bawah tenda: Implementasi umum

Kode generik memungkinkan Anda untuk menulis fungsi dan tipe yang fleksibel dan dapat digunakan kembali yang dapat bekerja dengan jenis apa pun, tunduk pada persyaratan yang Anda tetapkan. Anda dapat menulis kode yang menghindari duplikasi dan mengekspresikan maksudnya dengan cara yang jelas dan abstrak. - Dokumen cepat

Setiap orang yang menulis di Swift menggunakan obat generik. Array , Dictionary , Set - opsi paling dasar untuk menggunakan obat generik dari perpustakaan standar. Bagaimana mereka diwakili di dalam? Mari kita lihat bagaimana fitur dasar bahasa ini diimplementasikan oleh para insinyur Apple.


Parameter generik dapat dibatasi oleh protokol atau tidak terbatas, meskipun, pada dasarnya, generik digunakan bersama dengan protokol yang menggambarkan apa yang sebenarnya dapat dilakukan dengan parameter metode atau bidang tipe.


Untuk mengimplementasikan obat generik, Swift menggunakan dua pendekatan:


  1. Runtime-way - kode generik adalah pembungkus (Boxing).
  2. Cara kompiletime - kode generik dikonversi ke jenis kode khusus untuk pengoptimalan (Spesialisasi).

Tinju


Pertimbangkan metode sederhana dengan parameter generik protokol tidak terbatas:


 func test<T>(value: T) -> T { let copy = value print(copy) return copy } 

Kompilator cepat membuat satu blok kode tunggal yang akan dipanggil untuk bekerja dengan <T> . Yaitu, terlepas dari apakah kita menulis test(value: 1) atau test(value: "Hello") , kode yang sama akan dipanggil dan informasi tambahan tentang tipe <T> berisi semua informasi yang diperlukan akan ditransfer ke metode .


Sedikit yang dapat dilakukan dengan parameter protokol tidak terbatas tersebut, tetapi sudah menerapkan metode ini, Anda perlu tahu cara menyalin parameter, Anda perlu tahu ukurannya untuk mengalokasikan memori untuk itu dalam runtime, Anda perlu tahu cara menghancurkannya ketika parameter meninggalkan lapangan visibilitas. Value Witness Table ( VWT ) digunakan untuk menyimpan informasi ini. VWT dibuat pada tahap kompilasi untuk semua jenis dan kompiler menjamin bahwa dalam runtime akan ada tata letak objek. Biarkan saya mengingatkan Anda bahwa struktur dalam Swift diberikan oleh nilai, dan kelas dengan referensi, jadi hal-hal yang berbeda akan dilakukan untuk let copy = value dengan T == MyClass dan T == MyStruct .


Tabel nilai saksi

Artinya, memanggil metode test dengan melewati struktur yang dideklarasikan di sana pada akhirnya akan terlihat seperti ini:


 //  ,  metadata   let myStruct = MyStruct() test(value: myStruct, metadata: MyStruct.metadata) 

Segalanya menjadi sedikit lebih rumit ketika MyStruct sendiri merupakan struktur generik dan mengambil bentuk MyStruct<T> . Bergantung pada <T> di dalam MyStruct , metadata dan VWT akan berbeda untuk tipe MyStruct<Int> dan MyStruct<Bool> . Ini adalah dua jenis berbeda dalam runtime. Tetapi menciptakan metadata untuk setiap kombinasi yang mungkin dari MyStruct dan T sangat tidak efisien, jadi Swift sebaliknya, dan untuk kasus seperti itu membangun metadata dalam runtime saat dalam perjalanan. Kompiler membuat satu pola metadata untuk struktur generik, yang dapat dikombinasikan dengan tipe tertentu dan, sebagai hasilnya, menerima informasi tipe lengkap dalam runtime dengan VWT benar.


 //   ,  metadata   func test<T>(value: MyStruct<T>, tMetadata: T.Type) { //       let myStructMetadata = get_generic_metadata(MyStruct.metadataPattern, tMetadata) ... } let myStruct = MyStruct<Int>() test(value: myStruct) //   test(value: myStruct, tMetadata: Int.metadata) //      

Ketika kami menggabungkan informasi, kami mendapatkan metadata yang dapat kami kerjakan (menyalin, memindahkan, menghancurkan).


Ini masih sedikit lebih rumit ketika pembatasan protokol ditambahkan ke obat generik. Misalnya, kami membatasi <T> protokol yang Equatable . Biarkan itu menjadi metode yang sangat sederhana yang membandingkan dua argumen yang disampaikan. Hasilnya hanyalah pembungkus metode perbandingan.


 func isEquals<T: Equatable>(first: T, second: T) -> Bool { return first == second } 

Agar program dapat bekerja dengan benar, Anda harus memiliki penunjuk ke metode perbandingan, static func ==(lhs:T, rhs:T) . Bagaimana cara mendapatkannya? Jelas, transmisi VWT tidak cukup, tidak mengandung informasi ini. Untuk mengatasi masalah ini, ada Protocol Witness Table atau PWT . VWT ini mirip dengan VWT dan dibuat pada tahap kompilasi untuk protokol dan menjelaskan protokol ini.


 isEquals(first: 1, second: 2) //   //     isEquals(first: 1, // 1 second: 2, metadata: Int.metadata, // 2 intIsEquatable: Equatable.witnessTable) // 3 

  1. Dua argumen berlalu
  2. Pass metadata untuk Int sehingga Anda dapat menyalin / memindahkan / menghancurkan objek
  3. Kami meneruskan informasi yang mengimplementasikan Int Equatable .

Jika pembatasan memerlukan implementasi protokol lain, misalnya, T: Equatable & MyProtocol , maka informasi tentang MyProtocol akan ditambahkan dengan parameter berikut:


 isEquals(..., intIsEquatable: Equatable.witnessTable, intIsMyProtocol: MyProtocol.witnessTable) 

Menggunakan pembungkus untuk mengimplementasikan obat generik memungkinkan Anda untuk secara fleksibel menerapkan semua fitur yang diperlukan, tetapi memiliki overhead yang dapat dioptimalkan.


Spesialisasi generik


Untuk menghilangkan kebutuhan yang tidak perlu untuk mendapatkan informasi selama pelaksanaan program, apa yang disebut pendekatan spesialisasi generik digunakan. Ini memungkinkan Anda untuk mengganti pembungkus generik dengan tipe tertentu dengan implementasi spesifik. Misalnya, untuk dua panggilan ke isEquals(first: 1, second: 2) dan isEquals(first: "Hello", second: "world") , di samping implementasi "wrapper" utama, dua versi metode yang sama sekali berbeda untuk Int dan untuk String .


Kode sumber


Pertama, buat file generic.swift dan tulis fungsi generik kecil yang akan kita pertimbangkan.


 func isEquals<T: Equatable>(first: T, second: T) -> Bool { return first == second } isEquals(first: 10, second: 11) 

Sekarang Anda perlu memahami apa yang akhirnya berubah menjadi kompiler.
Ini dapat dilihat dengan jelas dengan mengkompilasi file .swift kami dalam Bahasa Swift Intermediate atau SIL .


Sedikit tentang SIL dan proses kompilasi


SIL adalah hasil dari salah satu dari beberapa tahap kompilasi cepat.


Pipa penyusun

Kode sumber .swift diteruskan ke Lexer, yang membuat pohon sintaksis abstrak ( AST ) dari bahasa tersebut, berdasarkan jenis pemeriksaan dan analisis semantik dari kode yang dilakukan. SilGen mengonversi AST ke SIL , yang disebut raw SIL , berdasarkan kode yang dioptimalkan dan canonical SIL dioptimalkan diperoleh, yang diteruskan ke IRGen untuk dikonversi ke IR - format khusus yang dimengerti LLVM , yang akan dikonversi ke , . .o yang , . , . SIL`.


Dan lagi ke obat generik


Buat file SIL dari kode sumber kami.


 swiftc generic.swift -O -emit-sil -o generic-sil.s 

Kami mendapatkan file baru dengan ekstensi *.s . Melihat ke dalam, kita akan melihat kode yang jauh lebih mudah dibaca daripada yang asli, tetapi masih relatif jelas.


Sil mentah

Temukan baris dengan komentar // isEquals<A>(first:second:) . Ini adalah awal dari deskripsi metode kami. Itu berakhir dengan komentar // end sil function '$s4main8isEquals5first6secondSbx_xtSQRzlF' . Nama Anda mungkin sedikit berbeda. Mari kita menganalisis deskripsi metode sedikit.


  • %0 dan %1 pada baris 21 masing-masing adalah parameter first dan second
  • Pada baris 24 kita mendapatkan informasi jenis dan meneruskannya ke %4
  • Pada baris 25 kita mendapatkan pointer ke metode perbandingan dari informasi jenis
  • on line 26 Kami memanggil metode dengan pointer, meneruskannya baik parameter dan ketik informasi
  • Pada baris 27 kami memberikan hasilnya.

Akibatnya, kita melihat: untuk melakukan tindakan yang diperlukan dalam implementasi metode generik, kita perlu memperoleh informasi dari deskripsi tipe <T> selama eksekusi program.


Kami melanjutkan langsung ke spesialisasi.


Dalam file SIL dikompilasi, segera setelah deklarasi metode isEquals umum, deklarasi khusus untuk tipe Int berikut.



SIL khusus

Pada baris 39, alih-alih mendapatkan metode dalam runtime dari informasi jenis, metode untuk membandingkan bilangan bulat "cmp_eq_Int64" segera dipanggil.


Agar metode untuk "mengkhususkan", optimasi harus diaktifkan . Anda juga perlu tahu itu


Pengoptimal hanya dapat melakukan spesialisasi jika definisi deklarasi generik terlihat di Modul saat ini ( Sumber )

Artinya, metode ini tidak dapat dikhususkan di antara berbagai modul Swift (misalnya, metode generik dari pustaka Cocoapods). Pengecualian adalah pustaka Swift standar, di mana tipe dasar seperti Array , Set and Dictionary . Semua obat generik dari pangkalan perpustakaan berspesialisasi dalam tipe tertentu.


Catatan: Atribut @inlinable dan @usableFromInline diimplementasikan di Swift 4.2, yang memungkinkan pengoptimal untuk melihat @usableFromInline metode dari modul lain dan sepertinya ada peluang untuk mengkhususkannya, tetapi perilaku ini tidak diuji oleh saya ( Sumber )


Referensi


  1. Deskripsi obat generik
  2. Optimasi dalam Swift
  3. Presentasi yang lebih rinci dan mendalam tentang topik tersebut.
  4. Artikel bahasa inggris

Source: https://habr.com/ru/post/id451704/


All Articles