
Dimulai dengan versi Gradle 4.7 dan Kotlin 1.3.30, menjadi mungkin untuk mendapatkan perakitan proyek inkremental yang dipercepat karena operasi yang benar dari pemrosesan tambahan anotasi. Dalam artikel ini, kami memahami bagaimana teori kompilasi tambahan dalam Gradle bekerja dalam teori, apa yang perlu dilakukan untuk melepaskan potensi penuhnya (tanpa kehilangan pembuatan kode pada saat yang sama), dan apa jenis peningkatan kecepatan majelis tambahan yang dapat dicapai dengan aktivasi pemrosesan tambahan penjelasan dalam praktik.
Cara kompilasi tambahan bekerja
Build tambahan dalam Gradle diimplementasikan pada dua level. Level pertama adalah untuk membatalkan dimulainya modul kompilasi ulang menggunakan kompilasi menghindari . Yang kedua adalah kompilasi tambahan langsung, meluncurkan kompiler dalam kerangka satu modul hanya pada file-file yang telah berubah, atau secara langsung bergantung pada file yang diubah.
Mari kita pertimbangkan kompilasi penghindaran pada contoh (diambil dari artikel dari Gradle) dari proyek tiga modul: aplikasi , inti dan utilitas .
Kelas utama modul aplikasi (tergantung pada inti ):
public class Main { public static void main(String... args) { WordCount wc = new WordCount(); wc.collect(new File(args[0]); System.out.println("Word count: " + wc.wordCount()); } }
Dalam modul inti (tergantung pada utils ):
public class WordCount {
Dalam modul utils :
public class IOUtils { void eachLine(File file, Callable<String> action) { try { try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
Urutan kompilasi pertama dari modul adalah sebagai berikut (sesuai dengan urutan dependensi):
1) utils
2) inti
3) aplikasi
Sekarang pertimbangkan apa yang terjadi ketika Anda mengubah implementasi internal kelas IOUtils:
public class IOUtils {
Perubahan ini tidak mempengaruhi modul ABI. ABI (Application Binary Interface) adalah representasi biner dari antarmuka publik dari modul yang dirakit. Dalam kasus di mana perubahan hanya berkaitan dengan implementasi internal modul dan tidak memengaruhi antarmuka publik dengan cara apa pun, Gradle akan menggunakan kompilasi penghindaran dan mulai mengkompilasi ulang hanya modul utilitas . Jika ABI modul utilitas terpengaruh (misalnya, metode publik tambahan muncul atau tanda tangan dari modul yang ada berubah), maka kompilasi modul inti juga akan dimulai, tetapi modul aplikasi yang bergantung pada inti tidak akan dikompilasi ulang secara transitif jika ketergantungan di dalamnya terhubung melalui implementasi .

Ilustrasi penghindaran kompilasi di tingkat modul proyek
Tingkat kenaikan kedua adalah kenaikan pada tingkat peluncuran kompiler untuk file yang diubah secara langsung di dalam masing-masing modul.
Sebagai contoh, tambahkan kelas baru ke modul inti :
public class NGrams {
Dan dalam utils :
public class StringUtils { static String sanitize(String dirtyString) { ... } }
Dalam hal ini, di kedua modul hanya perlu mengkompilasi ulang dua file baru (tanpa memengaruhi WordCount dan IOUtils yang ada dan tidak berubah), karena tidak ada ketergantungan antara kelas baru dan lama.
Dengan demikian, kompiler inkremental menganalisis dependensi antara kelas dan mengkompilasi ulang saja:
Membuat kode menggunakan APT dan KAPT mengurangi waktu yang diperlukan untuk menulis dan men-debug kode boilerplate, tetapi pemrosesan anotasi dapat secara signifikan meningkatkan waktu pembuatan. Untuk membuat keadaan menjadi lebih buruk, untuk waktu yang lama, pemrosesan anotasi secara fundamental mematahkan kemungkinan kompilasi tambahan di Gradle.
Setiap prosesor anotasi dalam proyek memberitahu informasi kompilator tentang daftar anotasi yang diprosesnya. Tetapi dari sudut pandang perakitan, pemrosesan anotasi adalah kotak hitam: Gradle tidak tahu apa yang akan dilakukan prosesor, khususnya, file mana yang akan dihasilkan dan di mana. Hingga Gradle 4.7, kompilasi inkremental secara otomatis dinonaktifkan pada set sumber yang menggunakan prosesor anotasi.
Dengan dirilisnya Gradle 4.7, kompilasi tambahan sekarang mendukung pemrosesan anotasi, tetapi hanya untuk APT. Di KAPT, dukungan untuk anotasi tambahan telah diperkenalkan dengan Kotlin 1.3.30. Itu juga memerlukan dukungan dari perpustakaan yang menyediakan prosesor anotasi. Pengembang prosesor anotasi memiliki kesempatan untuk secara eksplisit mengatur kategori prosesor, dengan demikian menginformasikan Gradle informasi yang diperlukan agar kompilasi tambahan berfungsi.
Kategori Prosesor Anotasi
Gradle mendukung dua kategori prosesor:
Mengisolasi - prosesor tersebut harus membuat semua keputusan untuk pembuatan kode hanya berdasarkan informasi dari AST yang dikaitkan dengan elemen anotasi tertentu. Ini adalah kategori tercepat dari prosesor anotasi, karena Gradle mungkin tidak me-restart prosesor dan menggunakan file yang dihasilkan sebelumnya jika tidak ada perubahan pada file sumber.
Agregasi - digunakan untuk prosesor yang membuat keputusan berdasarkan beberapa input (misalnya, analisis anotasi dalam beberapa file sekaligus atau berdasarkan studi AST, yang dapat dijangkau secara transparan dari elemen yang dianotasi). Setiap kali, Gradle akan memulai prosesor untuk file yang menggunakan anotasi dari prosesor agregasi, tetapi tidak akan mengkompilasi ulang file yang dihasilkannya jika tidak ada perubahan pada mereka.
Untuk banyak perpustakaan populer berdasarkan pembuatan kode, dukungan kompilasi tambahan sudah diterapkan di versi terbaru. Lihat daftar perpustakaan yang mendukungnya di sini .
Pengalaman kami menerapkan pemrosesan anotasi tambahan
Sekarang untuk proyek-proyek yang dimulai dari awal dan menggunakan versi terbaru dari perpustakaan dan plugins gradle, build incremental kemungkinan besar akan aktif secara default. Tetapi bagian terbesar dari peningkatan produktivitas perakitan dapat dicapai dengan penambahan pemrosesan anotasi pada proyek-proyek besar dan berumur panjang. Dalam hal ini, pembaruan versi besar-besaran mungkin diperlukan. Apakah ini sepadan dengan praktiknya? Ayo lihat!
Jadi, agar pemrosesan tambahan anotasi berfungsi, kita perlu:
- Gradle 4.7+
- Kotlin 1.3.30+
- Semua prosesor anotasi dalam proyek kami harus memiliki dukungannya. Ini sangat penting, karena jika dalam satu modul setidaknya satu prosesor tidak mendukung incrementality, Gradle akan menonaktifkannya untuk seluruh modul. Semua file dalam modul akan dikompilasi lagi setiap kali! Salah satu opsi alternatif untuk mendapatkan dukungan untuk kompilasi tambahan tanpa memutakhirkan versi adalah penghapusan semua kode menggunakan prosesor anotasi dalam modul terpisah. Dalam modul yang tidak memiliki prosesor anotasi, kompilasi tambahan akan berfungsi dengan baik
Untuk mendeteksi prosesor yang tidak memenuhi kondisi terakhir, Anda dapat menjalankan perakitan dengan flag -Pkapt.verbose = true . Jika Gradle terpaksa menonaktifkan pemrosesan anotasi tambahan untuk satu modul, maka dalam log pembangunan kita akan melihat pesan tentang prosesor mana dan di mana modul ini terjadi (lihat nama tugas):
> Task :common:kaptDebugKotlin w: [kapt] Incremental annotation processing requested, but support is disabled because the following processors are not incremental: toothpick.compiler.factory.FactoryProcessor (NON_INCREMENTAL), toothpick.compiler.memberinjector.MemberInjectorProcessor (NON_INCREMENTAL).
Pada proyek perpustakaan kami dengan prosesor anotasi non-tambahan, ada 3:
- Tusuk gigi
- Kamar
- IzinDatchatcher
Untungnya, perpustakaan ini didukung secara aktif, dan versi terbaru mereka sudah memiliki dukungan tambahan. Selain itu, semua prosesor anotasi dalam versi terbaru dari perpustakaan ini memiliki kategori yang optimal - isolasi. Dalam proses meningkatkan versi, saya harus berurusan dengan refactoring karena perubahan dalam API library Toothpick, yang mempengaruhi hampir setiap modul kita. Tetapi dalam kasus ini, kami beruntung, dan ternyata menjadi refactoring sepenuhnya secara otomatis menggunakan nama penggantian otomatis dari metode perpustakaan umum yang digunakan.
Perhatikan bahwa jika Anda menggunakan perpustakaan Room, Anda harus secara eksplisit melewati room.incremental: true flag ke prosesor anotasi. Sebuah contoh Di masa depan, pengembang Kamar berencana untuk mengaktifkan bendera ini secara default.
Untuk versi Kotlin 1.3.30-1.3.50, Anda harus mengaktifkan dukungan untuk pemrosesan anotasi tambahan secara eksplisit melalui kapt.incremental.apt = true dalam file properti gradle.properties proyek. Dimulai dengan versi 1.3.50, opsi ini disetel ke true secara default.
Profil perakitan tambahan
Setelah versi semua dependensi yang diperlukan telah dinaikkan, sekarang saatnya untuk menguji kecepatan build tambahan. Untuk melakukan ini, kami menggunakan seperangkat alat dan teknik berikut:
- Pemindaian gradle build
- gradle-profiler
- Untuk menjalankan skrip dengan pemrosesan anotasi inkremental yang diaktifkan dan dinonaktifkan, properti gradle kapt.incremental.apt = [true | false] digunakan
- Untuk hasil yang konsisten dan informatif, majelis dibesarkan di lingkungan CI yang terpisah. Inkrementalitas build direproduksi menggunakan gradle-profiler
gradle-profiler memungkinkan skrip deklaratif untuk skrip build tambahan. 4 skenario disusun berdasarkan kondisi berikut:
- Memodifikasi file memengaruhi / tidak memengaruhi ABI-nya
- Dukungan untuk pemrosesan anotasi tambahan on / off
Jalankan masing-masing skenario adalah urutan:
- Mulai ulang daemon gradle
- Luncurkan build-up builds
- Jalankan 10 rakitan tambahan, sebelum masing-masing file diubah dengan menambahkan metode baru (pribadi untuk perubahan non-ABI dan publik untuk perubahan ABI)
Semua build dilakukan dengan Gradle 5.4.1. File yang terlibat dalam perubahan mengacu pada salah satu modul inti proyek (umum), yang darinya 40 modul (termasuk inti dan fitur) bergantung langsung. File ini menggunakan anotasi untuk mengisolasi prosesor.
Juga perlu dicatat bahwa menjalankan benchmark dilakukan pada dua tugas gradle : ompileDebugSources dan assembleDebug . Yang pertama hanya memulai kompilasi file dengan kode sumber, tanpa melakukan pekerjaan apa pun dengan sumber daya dan menggabungkan aplikasi menjadi file .apk. Berdasarkan fakta bahwa kompilasi tambahan hanya memengaruhi file .kt dan .java, tugas compileDedugSource dipilih untuk pembandingan yang lebih terisolasi dan lebih cepat. Dalam kondisi pengembangan nyata, ketika Anda me-restart aplikasi, Android Studio menggunakan tugas assembleDebug , yang mencakup generasi penuh versi debug aplikasi.
Hasil benchmark
Di semua grafik yang dihasilkan oleh gradle-profiler, sumbu vertikal menunjukkan waktu rakitan tambahan dalam milidetik, dan sumbu horizontal menunjukkan angka mulai rakitan.
: compileDebugSource sebelum memperbarui prosesor anotasi

Waktu berjalan rata-rata untuk setiap skenario adalah 38 detik sebelum memperbarui prosesor anotasi ke versi yang mendukung inkrementalitas. Dalam hal ini, Gradle menonaktifkan dukungan untuk kompilasi tambahan, sehingga tidak ada perbedaan yang signifikan antara skrip.
: compileDebugSource setelah memperbarui prosesor anotasi

Pengurangan rata-rata dalam waktu perakitan karena inkrementalitas adalah 31% untuk perubahan ABI dan 32,5% untuk perubahan non-ABI. Dalam nilai absolut, sekitar 10 detik.
: assembleDebug setelah memperbarui prosesor anotasi

Untuk membangun versi debug penuh aplikasi pada proyek kami, penurunan rata-rata dalam waktu pembangunan karena peningkatan adalah 21,5% untuk perubahan ABI dan 23% untuk perubahan non-ABI. Secara absolut, kira-kira sama 10 detik, karena penambahan kompilasi kode sumber tidak mempengaruhi kecepatan perakitan sumber daya.
Build Scan Anatomy dalam Gradle Build Scan
Untuk pemahaman yang lebih dalam tentang bagaimana peningkatan dicapai selama kompilasi tambahan, kami membandingkan pemindaian majelis inkremental dan non-inkremental.
Dalam kasus kenaikan KAPT yang dinonaktifkan, bagian utama waktu pembuatan adalah kompilasi modul aplikasi, yang tidak dapat diparalelkan dengan tugas lain. Batas waktu untuk KAPT non-inkremental adalah sebagai berikut:

Eksekusi tugas: kaptDebugKotlin dari modul aplikasi kami membutuhkan waktu sekitar 8 detik dalam kasus ini.
Garis waktu untuk kasus dengan kenaikan KAPT diaktifkan:

Sekarang modul aplikasi telah dikompilasi ulang dalam waktu kurang dari satu detik. Penting untuk memperhatikan ketidakseimbangan visual dari skala dari dua pemindaian dalam picch di atas. Tugas yang tampak lebih pendek di gambar pertama tidak harus lebih lama di gambar kedua, di mana mereka tampak lebih lama. Tetapi sangat nyata seberapa besar proporsi kompilasi ulang modul-aplikasi menurun ketika Anda mengaktifkan KAPT tambahan. Dalam kasus kami, kami menang sekitar 8 detik pada modul ini dan tambahan sekitar 2 detik pada modul yang lebih kecil yang dikompilasi secara paralel.
Pada saat yang sama, total waktu eksekusi dari semua tugas * kapt untuk inkrementalitas dinonaktifkan dari anotasi pemrosesan adalah 1 menit dan 36 detik terhadap 55 detik ketika diaktifkan. Artinya, tanpa memperhitungkan perakitan paralel modul, keuntungannya lebih besar.
Perlu juga dicatat bahwa hasil benchmark di atas disiapkan pada lingkungan CI dengan kemampuan untuk menjalankan 24 thread paralel untuk perakitan. Pada lingkungan 8-utas, keuntungan dari mengaktifkan pemrosesan anotasi tambahan adalah sekitar 20-30 detik pada proyek kami.
Incremental vs (?) Paralel
Cara lain untuk mempercepat perakitan secara signifikan (baik inkremental dan bersih) adalah melakukan tugas gradle secara paralel dengan memecah proyek menjadi sejumlah besar modul yang digabungkan secara longgar. Dengan satu atau lain cara, modularisasi mewakili potensi yang jauh lebih besar untuk mempercepat majelis daripada menggunakan KAPT tambahan. Tetapi semakin monolitik proyek tersebut, dan semakin banyak generasi kode digunakan di dalamnya, semakin besar pula proses penambahan anotasi. Lebih mudah untuk mendapatkan efek incrementality lengkap rakitan daripada memecah aplikasi menjadi modul. Namun demikian, kedua pendekatan tersebut tidak saling bertentangan dan saling melengkapi.
Ringkasan
- Dimasukkannya pemrosesan tambahan anotasi pada proyek kami memungkinkan kami untuk mencapai peningkatan 20% dalam kecepatan pembangunan kembali lokal
- Untuk mengaktifkan pemrosesan anotasi tambahan, akan berguna untuk mempelajari log lengkap rakitan saat ini dan mencari pesan peringatan dengan teks "Permintaan pemrosesan anotasi tambahan diminta, tetapi dukungan dinonaktifkan karena prosesor berikut ini bukan tambahan ...". Diperlukan untuk meningkatkan versi perpustakaan ke versi dengan dukungan untuk pemrosesan anotasi tambahan dan memiliki versi Gradle 4.7+, Kotlin 1.3.30+
Bahan dan apa yang harus dibaca tentang topik tersebut