Saya tahu bahwa fungsi sebaris mungkin akan meningkatkan kinerja & menyebabkan kode yang dihasilkan bertambah, tetapi saya tidak yakin kapan benar untuk menggunakannya.
lock(l) { foo() }
Alih-alih membuat objek fungsi untuk parameter dan membuat panggilan, kompilator dapat mengeluarkan kode berikut. ( Sumber )
l.lock()
try {
foo()
}
finally {
l.unlock()
}
tetapi saya menemukan bahwa tidak ada objek fungsi yang dibuat oleh kotlin untuk fungsi non-inline. Mengapa?
/**non-inline function**/
fun lock(lock: Lock, block: () -> Unit) {
lock.lock();
try {
block();
} finally {
lock.unlock();
}
}
function
kotlin
inline-functions
holi-java
sumber
sumber
Jawaban:
Katakanlah Anda membuat fungsi orde tinggi yang menggunakan lambda tipe
() -> Unit
(tanpa parameter, tanpa nilai kembalian), dan mengeksekusinya seperti ini:Dalam bahasa Java, ini akan diterjemahkan menjadi sesuatu seperti ini (disederhanakan!):
Dan ketika Anda menyebutnya dari Kotlin ...
Di bawah tenda, sebuah instance dari
Function
akan dibuat di sini, yang membungkus kode di dalam lambda (sekali lagi, ini disederhanakan):Jadi pada dasarnya, memanggil fungsi ini dan meneruskan lambda ke sana akan selalu membuat instance dari suatu
Function
objek.Di sisi lain, jika Anda menggunakan
inline
kata kunci:Bila Anda menyebutnya seperti ini:
Tidak ada
Function
instance yang akan dibuat, sebagai gantinya, kode di sekitar pemanggilanblock
di dalam fungsi inline akan disalin ke situs panggilan, jadi Anda akan mendapatkan sesuatu seperti ini di bytecode:Dalam kasus ini, tidak ada instance baru yang dibuat.
sumber
noinline
dancrossinline
- lihat dokumennya .Izinkan saya menambahkan: "Kapan tidak digunakan
inline
" :1) Jika Anda memiliki fungsi sederhana yang tidak menerima fungsi lain sebagai argumen, tidak masuk akal untuk menyebariskannya. IntelliJ akan memperingatkan Anda:
2) Bahkan jika Anda memiliki fungsi "dengan parameter tipe fungsional", Anda mungkin menemukan kompilator yang memberi tahu Anda bahwa inlining tidak berfungsi. Pertimbangkan contoh ini:
Kode ini tidak dapat dikompilasi dengan kesalahan:
Alasannya adalah kompilator tidak dapat menyebariskan kode ini. Jika
operation
tidak dibungkus dalam sebuah objek (yang diimplikasikan olehinline
karena Anda ingin menghindari ini), bagaimana itu dapat ditugaskan ke variabel sama sekali? Dalam kasus ini, kompilator menyarankan untuk membuat argumennoinline
. Memilikiinline
fungsi dengan satunoinline
fungsi tidak masuk akal, jangan lakukan itu. Namun, jika ada beberapa parameter jenis fungsional, pertimbangkan untuk membuat sebaris beberapa di antaranya jika diperlukan.Berikut beberapa aturan yang disarankan:
noinline
untuk yang lain.reified
parameter tipe, yang mengharuskan Anda untuk menggunakannyainline
. Baca disini .sumber
inline
bisa berbahaya adalah ketika parameter fungsional dipanggil beberapa kali dalam fungsi sebaris, seperti di cabang bersyarat yang berbeda. Saya baru saja mengalami kasus di mana semua bytecode untuk argumen fungsional diduplikasi karena ini.Kasus yang paling penting ketika kita menggunakan pengubah inline adalah ketika kita mendefinisikan fungsi mirip-util dengan fungsi parameter. Pemrosesan koleksi atau string (seperti
filter
,map
ataujoinToString
) atau hanya fungsi yang berdiri sendiri adalah contoh sempurna.Inilah sebabnya mengapa pengubah sebaris sebagian besar merupakan pengoptimalan penting bagi pengembang perpustakaan. Mereka harus tahu cara kerjanya dan apa saja perbaikan dan biayanya. Kita harus menggunakan pengubah inline dalam proyek kita ketika kita mendefinisikan fungsi util kita sendiri dengan parameter jenis fungsi.
Jika kita tidak memiliki parameter tipe fungsi, parameter tipe reified, dan kita tidak membutuhkan pengembalian non-lokal, kemungkinan besar kita tidak boleh menggunakan pengubah inline. Inilah mengapa kami akan memiliki peringatan di Android Studio atau IDEA IntelliJ.
Juga, ada masalah ukuran kode. Menyebariskan fungsi yang besar dapat secara dramatis meningkatkan ukuran bytecode karena disalin ke setiap situs panggilan. Dalam kasus seperti itu, Anda dapat memfaktor ulang fungsi dan mengekstrak kode ke fungsi biasa.
sumber
Fungsi tingkat tinggi sangat membantu dan benar-benar dapat meningkatkan
reusability
kode. Namun, salah satu kekhawatiran terbesar tentang penggunaannya adalah efisiensi. Ekspresi Lambda dikompilasi ke kelas (seringkali kelas anonim), dan pembuatan objek di Java adalah operasi yang berat. Kita masih dapat menggunakan fungsi tingkat tinggi dengan cara yang efektif, sambil menjaga semua manfaat, dengan membuat fungsi sebaris.inilah fungsi inline ke dalam gambar
Ketika suatu fungsi ditandai sebagai
inline
, selama kompilasi kode, kompilator akan mengganti semua panggilan fungsi dengan badan fungsi yang sebenarnya. Selain itu, ekspresi lambda yang diberikan sebagai argumen diganti dengan tubuh aslinya. Mereka tidak akan diperlakukan sebagai fungsi, tetapi sebagai kode aktual.Singkatnya: - Inline -> daripada dipanggil, mereka digantikan oleh kode badan fungsi pada waktu kompilasi ...
Di Kotlin, menggunakan fungsi sebagai parameter fungsi lain (disebut fungsi tingkat tinggi) terasa lebih alami daripada di Java.
Namun, penggunaan lambda memiliki beberapa kelemahan. Karena mereka adalah kelas anonim (dan karenanya, objek), mereka memerlukan memori (dan bahkan mungkin menambah jumlah metode keseluruhan aplikasi Anda). Untuk menghindari hal ini, kita dapat menggunakan metode kita sebaris.
Dari contoh di atas : - Kedua fungsi ini melakukan hal yang persis sama - mencetak hasil dari fungsi getString. Yang satu sejajar dan yang lainnya tidak.
Jika Anda memeriksa kode java yang telah didekompilasi, Anda akan melihat bahwa metodenya benar-benar identik. Itu karena kata kunci inline adalah instruksi kepada kompilator untuk menyalin kode ke dalam situs panggilan.
Namun, jika kita meneruskan tipe fungsi apa pun ke fungsi lain seperti di bawah ini:
Untuk mengatasinya, kita bisa menulis ulang fungsi kita seperti di bawah ini:
Misalkan kita memiliki fungsi urutan yang lebih tinggi seperti di bawah ini:
Di sini, kompilator akan memberi tahu kita untuk tidak menggunakan kata kunci inline ketika hanya ada satu parameter lambda dan kita meneruskannya ke fungsi lain. Jadi, kita bisa menulis ulang fungsi di atas seperti di bawah ini:
Catatan : -kami harus menghapus kata kunci noinline juga karena hanya dapat digunakan untuk fungsi inline!
Misalkan kita memiliki fungsi seperti ini ->
Ini berfungsi dengan baik tetapi inti logika fungsi tersebut tercemar dengan kode pengukuran sehingga mempersulit kolega Anda untuk mengerjakan apa yang terjadi. :)
Inilah bagaimana fungsi sebaris dapat membantu kode ini:
Sekarang saya dapat berkonsentrasi untuk membaca apa maksud utama fungsi intercept () tanpa melewatkan baris kode pengukuran. Kami juga mendapat manfaat dari opsi untuk menggunakan kembali kode itu di tempat lain yang kami inginkan
inline memungkinkan Anda memanggil fungsi dengan argumen lambda dalam closure ({...}) daripada meneruskan lambda like measure (myLamda)
sumber
Satu kasus sederhana di mana Anda mungkin menginginkannya adalah ketika Anda membuat fungsi util yang mengambil blok penangguhan. Pertimbangkan ini.
Dalam kasus ini, timer kami tidak akan menerima fungsi penangguhan. Untuk mengatasinya, Anda mungkin tergoda untuk membuatnya ditangguhkan juga
Tapi kemudian hanya bisa digunakan dari fungsi coroutine / suspend itu sendiri. Kemudian Anda akan membuat versi asinkron dan versi non-asinkron dari utils ini. Masalahnya hilang jika Anda membuatnya sejajar.
Berikut adalah taman bermain kotlin dengan status error. Buat pengatur waktu sejajar untuk menyelesaikannya.
sumber