Performa Kotlin di Android

Mari kita bicara hari ini tentang kinerja Kotlin di Android dalam produksi. Mari kita lihat di bawah kap, terapkan optimasi rumit, bandingkan kode byte. Akhirnya, kami akan secara serius mendekati perbandingan dan mengukur tolok ukur.

Artikel ini didasarkan pada laporan oleh Alexander Smirnov di AppsConf 2017 dan akan membantu mencari tahu apakah mungkin untuk menulis kode di Kotlin, yang tidak akan kalah dengan kecepatan Java.


Tentang pembicara: Alexander Smirnov CTO di PapaJobs, menjalankan blog video Android di Faces , dan juga salah satu penyelenggara komunitas Mosdroid.

Mari kita mulai dengan harapan Anda.

Apakah menurut Anda Kotlin di runtime lebih lambat dari Java? Atau lebih cepat? Atau mungkin tidak banyak perbedaan? Bagaimanapun, keduanya bekerja pada bytecode yang disediakan oleh mesin virtual kepada kita.

Mari kita perbaiki. Secara tradisional, ketika pertanyaan tentang membandingkan kinerja muncul, semua orang ingin melihat tolok ukur dan angka tertentu. Sayangnya, untuk Android tidak ada JMH ( Java Microbenchmark Harness ), jadi kami tidak bisa mengukur seberapa keren itu bisa dilakukan di Jawa. Jadi apa yang bisa kita lakukan untuk melakukan pengukuran, seperti dijelaskan di bawah ini?

fun measure() : Long { val startTime = System.nanoTime() work() return System.nanoTime() - startTime } adb shell dumpsys gfxinfo %package_name% 

Jika Anda pernah mencoba mengukur kode Anda dengan cara ini, maka salah satu pengembang JMH akan sedih, menangis dan mendatangi Anda dalam mimpi - tidak pernah melakukan itu.

Di Android, Anda dapat membuat tolok ukur, khususnya, Google mendemonstrasikan ini di I / O tahun lalu. Mereka mengatakan bahwa mereka sangat meningkatkan mesin virtual, dalam hal ini ART, dan jika pada Android 4.1 satu alokasi objek membutuhkan sekitar 600-700 nanodetik, maka pada versi kedelapan akan membutuhkan sekitar 60 nanodetik. Yaitu mereka dapat mengukurnya dengan akurasi seperti itu di mesin virtual. Mengapa kami tidak dapat melakukan keduanya - kami tidak memiliki alat seperti itu.

Jika kita melihat semua dokumentasi, maka satu-satunya yang dapat kita temukan adalah rekomendasi di atas, bagaimana mengukur UI:

adb shell dumpsys gfxinfo% package_name%

Sebenarnya, mari kita lakukan dengan cara ini dan lihat pada akhirnya apa yang akan diberikannya. Tetapi pertama-tama, kita akan menentukan apa yang akan kita ukur dan apa lagi yang bisa kita lakukan.

Pertanyaan selanjutnya. Menurut Anda di mana kinerja itu penting ketika Anda membuat aplikasi kelas satu?

  1. Di mana-mana.
  2. Utas UI.
  3. Tampilan kustom + animasi.




Saya paling suka opsi pertama, tetapi kemungkinan besar diyakini bahwa tidak mungkin untuk membuat semua kode bekerja sangat, sangat cepat dan penting bahwa setidaknya tidak ada tampilan UiThread atau kustom. Saya juga setuju dengan ini - ini sangat, sangat penting. Fakta bahwa dalam aliran JSON Anda yang terpisah akan deserialized selama 10 milidetik lebih lama tidak akan ada yang memperhatikan.

Psikologi Gestalt mengatakan bahwa ketika kita berkedip, selama sekitar 150-300 milidetik, mata manusia tidak fokus dan tidak melihat apa yang sebenarnya terjadi di sana. Dan 10 milidetik cuaca ini tidak. Tetapi jika kita kembali ke psikologi gestalt, penting bukan apa yang benar-benar saya lihat dan apa yang sebenarnya terjadi, tetapi apa yang saya pahami sebagai pengguna itu penting.

Yaitu jika kita membuat pengguna berpikir bahwa dia memiliki segalanya dengan sangat, sangat cepat, tetapi sebenarnya itu hanya akan dipukuli dengan indah, misalnya, dengan bantuan animasi yang indah, maka dia akan puas, bahkan jika kenyataannya tidak.

Motif psikologi Gestalt di iOS telah bergerak cukup lama. Oleh karena itu, jika Anda mengambil dua aplikasi dengan waktu pemrosesan yang sama, tetapi pada platform yang berbeda, dan menempatkannya berdampingan, akan terlihat bahwa di iOS semuanya lebih cepat. Animasi di iOS proses sedikit lebih cepat, animasi sebelumnya dimulai saat startup dan banyak animasi lainnya, sehingga indah.

Jadi aturan pertama adalah memikirkan pengguna.

Dan untuk aturan kedua, Anda harus membenamkan diri dalam hardcore.

GAYA KOTLIN


Untuk secara jujur ​​mengevaluasi kinerja Kotlin, kami akan membandingkannya dengan Jawa. Karena itu, ternyata tidak mungkin mengukur beberapa hal yang hanya ada di Kotlin, misalnya:

  • Koleksi Api.
  • Metode parameter default.
  • Kelas data.
  • Jenis yang telah diverifikasi.
  • Coroutine.

API koleksi yang disediakan oleh Kotlin sangat keren, sangat cepat. Di Jawa, ini sama sekali tidak ada, hanya ada implementasi yang berbeda. Misalnya, pustaka Liteweight Stream API akan lebih lambat karena melakukan segalanya sama seperti Kotlin, tetapi dengan satu atau dua alokasi tambahan untuk operasi, karena semuanya berubah menjadi objek tambahan.

Jika kita mengambil Stream API dari Java 8, itu akan bekerja lebih lambat daripada Kotlin Collection API, tetapi dengan satu syarat - tidak ada kelumpuhan seperti itu di API Pengumpulan, jika kita memasukkan paralel, pada volume besar data Stream API, Java akan mem-bypass Kotlin Collection API. Karena itu, kami tidak dapat membandingkan hal-hal seperti itu, karena kami melakukan perbandingan dengan tepat dari sudut pandang Android.

Hal kedua, yang menurut saya, tidak dapat dibandingkan, adalah parameter standar Metode - fitur yang sangat keren, yang, omong-omong, ada di Dart. Ketika Anda memanggil beberapa metode, itu mungkin memiliki beberapa parameter yang mungkin mengambil beberapa nilai, tetapi mungkin NULL. Dan karena itu, Anda tidak membuat 10 metode yang berbeda, tetapi lakukan satu metode dan katakan bahwa salah satu parameter bisa NULL, dan di masa depan menggunakannya tanpa parameter apa pun. Yaitu dia akan melihat, parameternya telah datang, atau dia belum datang. Sangat nyaman karena Anda dapat menulis kode yang jauh lebih sedikit, tetapi ketidaknyamanannya adalah Anda harus membayarnya. Ini adalah gula sintaksis: Anda, sebagai pengembang, berpikir bahwa ini adalah satu metode API, tetapi pada kenyataannya, di bawah tenda, setiap variasi metode dengan parameter yang hilang dihasilkan dalam bytecode. Dan masing-masing metode ini juga memeriksa sedikit demi sedikit apakah parameter ini telah tiba. Jika itu datang, maka ok, jika tidak, maka kami membuat topeng bit, dan tergantung pada topeng bit ini, metode asli yang Anda tulis sebenarnya disebut. Operasi bitwise, semua jika / selain biaya sedikit uang, tetapi sangat sedikit, dan itu normal bahwa Anda harus membayar untuk kenyamanan. Menurut saya ini benar-benar normal.

Item berikutnya yang tidak dapat dibandingkan adalah kelas data .

Semua orang menangis bahwa di Jawa ada parameter yang ada kelas model. Yaitu Anda mengambil parameter dan melakukan lebih banyak metode, getter, dan setter untuk semua parameter ini. Ternyata untuk kelas dengan sepuluh parameter, Anda masih membutuhkan setumpuk getter, setter, dan banyak lagi. Apalagi, jika Anda tidak menggunakan generator, maka Anda harus menulis dengan tangan Anda, yang pada umumnya mengerikan.

Kotlin memungkinkan Anda menjauh dari itu semua. Pertama, karena ada properti di Kotlin, Anda tidak perlu menulis getter dan setter. Tidak memiliki parameter kelas, semua properti . Bagaimanapun, kami pikir begitu. Kedua, jika Anda menulis bahwa ini adalah kelas Data, banyak hal lain yang akan dihasilkan. Misalnya, equals (), toStrung () / hasCode (), dll.

Tentu saja, ini juga memiliki kekurangan. Misalnya, saya tidak perlu semua 20 parameter kelas data saya untuk dibandingkan sekaligus dalam equals saya (), saya hanya perlu membandingkan 3. Seseorang tidak menyukai semua ini karena kinerja hilang pada ini, dan di samping itu, banyak yang dihasilkan fungsi layanan, dan kode yang dikompilasi cukup banyak. Artinya, jika Anda menulis semuanya dengan tangan, akan ada lebih sedikit kode daripada jika Anda menggunakan kelas data.

Saya tidak menggunakan kelas data untuk alasan lain. Sebelumnya, ada batasan pada perluasan kelas-kelas seperti itu dan sesuatu yang lain. Sekarang semua orang lebih baik dengan ini, tetapi kebiasaan itu tetap ada.

Apa yang sangat, sangat keren di Kotlin, dan apa yang selalu lebih cepat daripada Java? Ini adalah tipe Reified , yang, omong-omong, juga ada di Dart.

Anda tahu bahwa ketika Anda menggunakan generik, tipe penghapusan dihapus pada tahap kompilasi dan di runtime Anda tidak lagi tahu objek apa yang sebenarnya digunakan generik ini.

Dengan jenis Reified, Anda tidak perlu menggunakan refleksi di banyak tempat ketika Anda membutuhkannya di Jawa, karena dengan metode sebaris itu adalah dengan Reified yang Anda tahu tentang jenisnya, dan karenanya Anda tidak menggunakan refleksi dan kode Anda bekerja lebih cepat. Keajaiban.

Dan ada Coroutine . Mereka sangat keren, saya sangat menyukainya, tetapi pada saat pertunjukan mereka hanya dimasukkan dalam versi alpha, jadi tidak mungkin untuk membuat perbandingan yang benar dengan mereka.

BIDANG


Jadi mari kita lanjutkan, beralih ke apa yang bisa kita bandingkan dengan Jawa dan apa yang bisa kita pengaruhi secara umum.

 class Test { var a = 5 var b = 6 val c = B() fun work () { val d = a + b val e = ca + cb } } class B (@JvmField var a: Int = 5,var b: Int = 6) 

Seperti yang saya katakan, kita tidak memiliki parameter untuk kelas, kita memiliki properti.

Kami memiliki var, kami memiliki val, kami memiliki kelas eksternal, salah satu sifatnya adalah @JvmField, dan kami akan melihat apa yang sebenarnya terjadi dengan fungsi work (): kami menjumlahkan nilai bidang a dan bidang b dari kelas kami sendiri dan nilai-nilai bidang a dan bidang b dari kelas luar, yang ditulis dalam bidang yang tidak dapat diubah c.

Pertanyaannya adalah apa, pada kenyataannya, akan dipanggil dalam d = a + b. Kita semua tahu bahwa properti ini satu kali, pengambil kelas ini akan dipanggil untuk parameter ini.

  L0 LINENUMBER 10 L0 ALOAD 0 GETFIELD kotlin/Test.a : I ALOAD 0 GETFIELD kotlin/Test.b : I IADD ISTORE 1 

Tetapi jika kita melihat bytecode, kita akan melihat bahwa getfield sebenarnya sedang diakses. Artinya, ini dalam bytecode bukan panggilan ke fungsi InvokeVirtual, tetapi akses langsung ke lapangan. Pada awalnya tidak ada yang dijanjikan kepada kami, bahwa kami memiliki semua properti, bukan ladang. Ternyata Kotlin menipu kita, ada permohonan langsung.

Apa yang terjadi jika kita melihat bytecode apa yang dihasilkan untuk baris lain: val e = ca + cb?

  L1 LINENUMBER 11 L1 ALOAD 0 GETFIELD kotlin/Test.c : Lkotlin/B; GETFIELD kotlin/Ba : I ALOAD 0 GETFIELD kotlin/Test.c : Lkotlin/B; INVOKEVIRTUAL kotlin/B.getB ()I IADD ISTORE 2 

Sebelumnya, jika Anda mengakses properti pribadi, maka Anda selalu memiliki panggilan InvokeVirtual. Jika ini adalah properti pribadi, maka akses ke sana adalah melalui GetField. GetField jauh lebih cepat daripada InvokeVirtual, spesifikasi dari Android mengklaim bahwa mengakses bidang secara langsung adalah 3-7 kali lebih cepat. Karena itu, disarankan agar Anda selalu merujuk ke Field, dan tidak melalui pengambil atau setter. Sekarang, terutama di mesin virtual ART kedelapan, sudah ada angka yang berbeda, tetapi jika Anda masih mendukung 4.1, ini akan benar.

Oleh karena itu, ternyata masih bermanfaat bagi kita untuk memiliki GetField, dan bukan InvokeVirtual.

Sekarang, Anda dapat mencapai GetField jika Anda mengakses properti dari kelas Anda sendiri, atau jika ini adalah properti publik, Anda harus mengatur @JvmField. Kemudian, persis sama dalam bytecode akan menjadi panggilan GetField, yang 3-7 kali lebih cepat.

Jelas bahwa di sini kita berbicara dalam nanodetik dan, dengan satu takhta, itu sangat, sangat kecil. Tetapi, di sisi lain, jika Anda melakukannya di utas UI, misalnya, dalam metode ondraw Anda mengakses beberapa jenis tampilan, maka ini akan mempengaruhi rendering setiap frame, dan Anda dapat melakukannya sedikit lebih cepat.

Jika kita menjumlahkan semua optimasi, maka jumlah itu dapat memberikan sesuatu.

STATIC!?


Bagaimana dengan statika? Kita semua tahu bahwa di Kotlin statis adalah objek pendamping. Sebelumnya, Anda mungkin menambahkan semacam tag, misalnya, public static, final static, dll., Jika Anda mengonversinya ke kode Kotlin, Anda akan mendapatkan objek pendamping, yang akan menulis sesuatu seperti berikut:

  companion object { var k = 5 fun work2() : Int = 42 } 

Apakah Anda pikir entri ini identik dengan deklarasi final statis standar di Jawa? Apakah ini statis atau tidak?

Ya, memang, Kotlin menyatakan bahwa ini dia di Kotlin - statis, objek itu mengatakan bahwa itu statis. Pada kenyataannya, ini tidak statis.

Jika kita melihat bytecode yang dihasilkan, kita akan melihat yang berikut:

  L2 LINENUMBER 21 L2 GETSTATIC kotlin/Test.Companion : Lkotlin/Test$Companion; INVOKEVIRTUAL kotlin/Test$Companion.getK ()I GETSTATIC kotlin/Test.Companion : Lkotlin/Test$Companion; INVOKEVIRTUAL kotlin/Test$Companion.work2 ()I IADD ISTORE 3 

Test.Companion dihasilkan, objek tunggal yang membuat instance dibuat, instance ini ditulis ke bidangnya sendiri. Setelah itu, akses ke salah satu objek pendamping terjadi melalui objek ini. Dia mengambil getstatic, yaitu instance statis dari kelas ini, dan memanggil fungsi getK invokevirtual di atasnya, dan persis sama untuk fungsi work2. Jadi kami mendapatkan bahwa itu tidak statis.

Ini penting, karena pada JVM yang lebih lama, invokestatik sekitar 30% lebih cepat daripada invokevirtual. Sekarang, tentu saja, di HotSpot, virtualisasi yang dioptimalkan akan sangat keren, dan itu hampir tidak terlihat. Namun demikian, Anda harus mengingat ini, terutama karena ada satu alokasi tambahan, dan lokasi tambahan pada 4ST1 adalah 700 nanodetik, terlalu banyak.

Mari kita lihat kode Java yang keluar jika Anda melakukan reverse-deploy bytecode:

 private static int k = 5; public static final Test.Companion Companion = new Test.Companion((DefaultConstructorMarker)null); public static final class Companion { public final int getK() { return Test.k;} public final void setK(int var1) { Test.k = var1; } public final int work2() { return 42; } private Companion() { } // $FF: synthetic method public Companion(DefaultConstructorMarker $constructor_marker) { this(); } } 

Bidang statis dibuat, implementasi final statis objek Pendamping, getter dan setter dibuat, dan, seperti yang Anda lihat, merujuk pada bidang statis di dalam, metode statis tambahan muncul. Semuanya cukup menyedihkan.

Apa yang bisa kita lakukan, memastikan itu tidak statis? Kita dapat mencoba menambahkan @JvmField dan @JvmStatic dan melihat apa yang terjadi.

 val i = k + work2() companion object { @JvmField var k = 5 JvmStatic fun work2() : Int = 42 } 

Saya akan mengatakan segera bahwa Anda tidak akan pergi dari @JvmStatic, itu akan menjadi objek yang sama, karena ini adalah objek pendamping, akan ada alokasi tambahan dari objek ini dan akan ada panggilan tambahan.

 private static int k = 5; public static final Test.Companion Companion = new Test.Companion((DefaultConstructorMarker)null); public static final class Companion { @JvmStatic public final int work2() { return 42; } private Companion() {} // $FF: synthetic method public Companion(DefaultConstructorMarker $constructor_marker) { this(); } } 

Tetapi panggilan akan berubah hanya untuk k, karena itu akan menjadi @JvmField, itu akan diambil secara langsung sebagai getstatic, getter dan setter tidak akan lagi dihasilkan. Tetapi untuk fungsi work2 tidak ada yang akan berubah.

  L2 LINENUMBER 21 L2 GETSTATIC kotlin/Test.k : I GETSTATIC kotlin/Test.Companion : Lkotlin/Test$Companion; INVOKEVIRTUAL kotlin/Test$Companion.work2 ()I IADD ISTORE 3 

Opsi kedua tentang cara membuat statis diusulkan dalam dokumentasi Kotlin, sehingga dikatakan bahwa kita bisa membuat objek, dan ini akan menjadi kode statis.

 object A { fun test() = 53 } 

Pada kenyataannya, ini juga tidak demikian.

 L3 LINENUMBER 23 L3 GETSTATIC kotlin/A.INSTANCE : Lkotlin/A; INVOKEVIRTUAL kotlin/A.test ()I POP 

Ternyata kami membuat panggilan instan getstatic dari singletone, yang dibuat, dan memanggil metode virtual yang sama persis.

Satu-satunya cara kita dapat mencapai invokestatik adalah Fungsi Orde Tinggi. Ketika kita hanya menulis beberapa fungsi di luar kelas, misalnya, test2 akan benar-benar disebut statis.

  fun test2() = 99 L4 LINENUMBER 24 L4 INVOKESTATIC kotlin/TestKt.test2 ()I POP 

Selain itu, hal yang paling menarik adalah bahwa sebuah kelas akan dibuat, sebuah objek, dalam hal ini testKt, itu akan menghasilkan objek untuk dirinya sendiri, itu akan menghasilkan fungsi yang dimasukkan ke objek ini, dan sekarang akan disebut sebagai invokestatic.

Mengapa ini dilakukan tidak bisa dipahami. Banyak yang tidak senang dengan ini, tetapi ada yang menganggap implementasi seperti itu cukup normal. Karena mesin virtual, termasuk. Seni meningkat, sekarang tidak begitu kritis. Di Android versi kedelapan, seperti halnya pada HotSpot, semuanya dioptimalkan, tetapi hal-hal kecil ini sedikit mempengaruhi kinerja secara keseluruhan.

KEWAJIBAN


 fun test(first: String, second: String?) : String { second ?: return first return "$first $second" } 

Ini adalah contoh menarik berikutnya. Tampaknya kami mencatat bahwa yang kedua dapat dibatalkan, dan harus diperiksa sebelum melakukan sesuatu dengannya. Dalam hal ini, saya berharap bahwa kita punya satu jika. Ketika kode ini digunakan jika yang kedua tidak sama dengan nol, maka saya pikir eksekusi akan berjalan lebih jauh dan hanya output pertama.

Bagaimana ini benar-benar terungkap dalam kode java? Sebenarnya akan ada cek.

 @NotNull public final String test(@NotNull String first,@Nullable String second) { Intrinsics.checkParameterIsNotNull(first, "first"); return second != null ? (first + " " + second) : first; } 

Kami akan mendapatkan Intrinsics pada awalnya. Katakanlah saya mengatakan yang ini

Jika akan berkembang menjadi operator ternary. Tapi selain itu, meskipun kami bahkan menetapkan bahwa parameter pertama tidak dapat nullable, masih akan diperiksa melalui Intrinsics.

Intrinsics adalah kelas internal di Kotlin yang memiliki serangkaian parameter dan pemeriksaan tertentu. Dan setiap kali Anda membuat parameter metode non-nullable, itu tetap memeriksanya. Mengapa Kemudian, kami bekerja di Interop Java, dan mungkin Anda berharap tidak akan dibatalkan di sini, tetapi dengan Java, itu akan datang dari suatu tempat.

Jika Anda memeriksa ini, ia akan pergi lebih jauh di sepanjang kode, dan kemudian setelah 10-20 panggilan metode, Anda akan melakukan sesuatu dengan parameter itu, meskipun mungkin tidak dapat dibatalkan, tetapi karena beberapa alasan itu ternyata. Segalanya akan jatuh cinta pada Anda, dan Anda tidak akan dapat memahami apa yang sebenarnya terjadi. Untuk menghindari situasi ini, setiap kali Anda melewati parameter nol, Anda masih harus memeriksanya. Dan jika itu nullable, maka akan ada pengecualian.

Cek ini juga bernilai sesuatu, dan jika ada banyak, maka itu tidak akan bagus.

Tetapi pada kenyataannya, jika kita berbicara tentang HotSpot, maka 10 panggilan dari Intrinsik ini akan memakan waktu sekitar empat nanodetik. Ini sangat, sangat kecil, dan Anda tidak perlu khawatir tentang ini, tetapi ini merupakan faktor yang menarik.

PRIMITIF


Di Jawa ada yang namanya primitif. Di Kotlin, seperti yang kita semua tahu, tidak ada primitif, kami selalu beroperasi dengan benda. Di Jawa, mereka digunakan untuk memberikan kinerja yang lebih tinggi untuk objek pada beberapa perhitungan minor. Untuk menambahkan dua objek jauh lebih mahal daripada menambahkan dua primitif. Pertimbangkan sebuah contoh.

  var a = 5 var b = 6 var bOption : Int? = 6 

Ada tiga angka, untuk dua yang pertama bukan tipe nol akan disimpulkan, dan sekitar yang ketiga kita katakan bahwa itu bisa nullable.

  private int a = 5; private int b = 6; @Nullable private Integer bOption = Integer.valueOf(6); 

Jika Anda melihat bytecode dan melihat kode Java mana yang dihasilkan, dua angka pertama bukan nol, dan karena itu mereka bisa primitif. Tetapi primitif tidak dapat berisi Null, hanya objek yang dapat melakukan ini, sehingga objek akan dihasilkan untuk angka ketiga.

AUTOBOXING


Saat Anda bekerja dengan primitif dan melakukan operasi dengan primitif dan non-primitif, Anda harus menerjemahkan salah satunya menjadi primitif atau objek.

Dan, tampaknya, tidak mengherankan bahwa jika Anda melakukan operasi dengan nullable dan tidak nullable di Kotlin, maka Anda kehilangan sedikit dalam kinerja. Apalagi, jika ada banyak operasi seperti itu, maka Anda kehilangan banyak.

  val a: String? = null var b = a?.isBlank() == true 

Lihat di mana Boxing / Unboxing akan berada di sini? Saya juga tidak melihat sampai saya melihat bytecode.

 if (a != null && a.isBlank()) true else false 

Sebenarnya, saya berharap bahwa akan ada sesuatu seperti perbandingan ini: jika string tidak nol dan jika kosong, maka setel ke true, jika tidak maka setel ke false. Semuanya tampak sederhana, tetapi dalam kenyataannya kode berikut ini dihasilkan:

 String a = (String)null; boolean b = Intrinsics.areEqual(a != null ? Boolean.valueOf(StringsKt.isBlank((CharSequence)a)) : null, Boolean.valueOf(true)); 

Ayo lihat ke dalam. Variabel a diambil, ia dilemparkan ke dalam CharSequence, setelah dilemparkan, yang juga telah dihabiskan untuk beberapa waktu, pemeriksaan lain disebut - StringsKt.isBlank - ini adalah bagaimana fungsi ekstensi untuk CharSequence ditulis, jadi ia dilemparkan dan dikirim. Karena ekspresi pertama dapat nullable, itu mengambil dan melakukan Boxing, dan membungkus semuanya dalam Boolean.valueOf. Oleh karena itu, primitif sejati juga menjadi objek, dan hanya setelah itu verifikasi sudah berlangsung dan Intrinsics.areEqual dipanggil.

Tampaknya operasi yang sederhana, tetapi hasil yang tidak terduga. Bahkan, ada beberapa hal seperti itu. Tetapi ketika Anda dapat memiliki nullable / not nullable, Anda dapat menghasilkan cukup banyak hal-hal seperti itu, dan yang Anda tidak akan pernah harapkan. Karena itu, saya sarankan Anda menjauh dari ketidakjelasan sesegera mungkin. Yaitu datang ke kekebalan nilai sedini mungkin dan menjauh dari nullable sehingga Anda beroperasi tidak nol secepat mungkin.

Loop


Hal menarik berikutnya.

Anda dapat menggunakan biasa untuk, yang di Jawa, tetapi Anda juga dapat menggunakan API nyaman baru - menulis enumerasi elemen dalam daftar segera. , work, it - .

 list.forEach { work(it * 2) } 

. , . , Google, , ArrayList for 3 , . .

, ArrayList, — foreach.

 inline fun <reified T> List<T>.foreach(crossinline action: (T) -> Unit): Unit { val size = size var i = 0 while (i < size) { action(get(i)) i++ } } list.foreach { } 

API, - . , Kotlin: extension , «», reified, .. , , , crossinline. , , . 3 , Android Google.

RANGES


Ranges.

 inline fun <reified T> List<T>.foreach(crossinline action: (T) -> Unit): Unit { val size = size for(i in 0..size) { work(i * 2) } } 

: Unit -. −1, until , , . , , ranges. Yaitu , . step. .

INTRINSICS


- Intrinsics, :

 class Test { fun concat(first: String, second: String) = "$first $second" } 

Intrinsics — second, first.

 public final class Test { @NotNull public final String concat(@NotNull String first, @NotNull String second) { Intrinsics.checkParameterIsNotNull(first, "first"); Intrinsics.checkParameterIsNotNull(second, "second"); return first + " " + second; } } 

, gradle. , - 4 , . Kotlin UI, , nullable, Kotlin :

kotlinc -Xno-call-assertions -Xno-param-assertions Test.kt

Intrinsics, , .

, , . — Xno-param-assertions — Intrinsics, .

, , , , , . , , , .

REDEX


, , , Proguard. , 99% , , . Android 8.0 , . , .

, Proguard, Facebook, Redex . -, , . , Jvm Fields , .

, Redex . , , , Proguard, , . Redex 7% APK. , .

BENCHMARKS


. , , . , . , dumpsys gfxinfo , . github github.com/smred .

, Huawei.


. — , . , , 0,04 . , , — , .


Kotlin, . , , . - , Kotlin , Java. , , , , . .


, , , , Kotlin Java. , - , , , , , .


, : - Kotlin , .. . , . - - — 2 , Galaxy S6, .


Google Pixel. , 0,1 .



, , ,

  • UI custom view.
  • onmeasure-onlayout-ondraw. autoboxing, not null ..
  • Kotlin, Java , .
  • — .

, , . , , , , Kotlin, . , Kotlin .

, .

brand new AppsConf , Android . , . , 8 9 .

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


All Articles