Tim Android Runtime (ART) telah mengurangi waktu kompilasi sebesar 18% tanpa mengurangi kualitas kode yang dikompilasi atau regresi memori puncak. Peningkatan ini adalah bagian dari inisiatif tahun 2025 kami untuk meningkatkan waktu kompilasi tanpa mengorbankan penggunaan memori atau kualitas kode yang dikompilasi.
Mengoptimalkan kecepatan waktu kompilasi sangat penting untuk ART. Misalnya, saat mengompilasi tepat waktu (JIT), hal ini secara langsung memengaruhi efisiensi aplikasi dan performa perangkat secara keseluruhan. Kompilasi yang lebih cepat akan mengurangi waktu sebelum pengoptimalan dimulai, sehingga menghasilkan pengalaman pengguna yang lebih lancar dan responsif. Selain itu, untuk JIT dan ahead-of-time (AOT), peningkatan kecepatan waktu kompilasi akan mengurangi konsumsi resource selama proses kompilasi, sehingga meningkatkan masa pakai baterai dan termal perangkat, terutama pada perangkat kelas bawah.
Beberapa peningkatan kecepatan waktu kompilasi ini diluncurkan dalam rilis Android Juni 2025, dan sisanya akan tersedia dalam rilis Android akhir tahun. Selain itu, semua pengguna Android di versi 12 dan yang lebih tinggi memenuhi syarat untuk menerima peningkatan ini melalui update utama.
Mengoptimalkan compiler pengoptimalan
Mengoptimalkan compiler selalu merupakan permainan kompromi. Anda tidak bisa mendapatkan kecepatan secara gratis; Anda harus mengorbankan sesuatu. Kami menetapkan tujuan yang sangat jelas dan menantang bagi diri kami sendiri: membuat compiler lebih cepat, tetapi melakukannya tanpa menimbulkan regresi memori dan, yang terpenting, tanpa menurunkan kualitas kode yang dihasilkannya. Jika kompilernya lebih cepat, tetapi aplikasi berjalan lebih lambat, kita telah gagal.
Satu-satunya sumber daya yang bersedia kami gunakan adalah waktu pengembangan kami sendiri untuk menggali lebih dalam, menyelidiki, dan menemukan solusi cerdas yang memenuhi kriteria ketat ini. Mari kita cermati cara kami bekerja untuk menemukan area yang perlu ditingkatkan, serta menemukan solusi yang tepat untuk berbagai masalah.
Menemukan kemungkinan pengoptimalan yang bermanfaat
Sebelum dapat mulai mengoptimalkan metrik, Anda harus dapat mengukurnya. Jika tidak, Anda tidak akan pernah yakin apakah Anda telah meningkatkannya atau tidak. Untungnya bagi kita, kecepatan waktu kompilasi cukup konsisten selama Anda melakukan beberapa tindakan pencegahan seperti menggunakan perangkat yang sama yang Anda gunakan untuk mengukur sebelum dan sesudah perubahan, dan memastikan Anda tidak membatasi termal perangkat Anda. Selain itu, kami juga memiliki pengukuran deterministik seperti statistik compiler yang membantu kami memahami apa yang terjadi di balik layar.
Karena sumber daya yang kami korbankan untuk peningkatan ini adalah waktu pengembangan, kami ingin dapat melakukan iterasi secepat mungkin. Artinya, kami mengambil beberapa aplikasi representatif (campuran aplikasi pihak pertama, aplikasi pihak ketiga, dan sistem operasi Android itu sendiri) untuk membuat prototipe solusi. Kemudian, kami memverifikasi bahwa penerapan akhir tersebut bermanfaat dengan pengujian manual dan otomatis secara luas.
Dengan kumpulan APK pilihan tersebut, kita akan memicu kompilasi manual secara lokal, mendapatkan profil kompilasi, dan menggunakan pprof untuk memvisualisasikan waktu yang kita habiskan.
Contoh flame graph profil di pprof
Alat pprof sangat canggih dan memungkinkan kita membagi, memfilter, dan mengurutkan data untuk melihat, misalnya, fase atau metode compiler mana yang paling banyak memakan waktu. Kita tidak akan membahas pprof secara mendetail; cukup ketahui bahwa jika batang lebih besar, berarti kompilasi membutuhkan lebih banyak waktu.
Salah satu tampilan ini adalah “bottom up” di mana Anda dapat melihat metode mana yang membutuhkan waktu paling lama. Pada gambar di bawah, kita dapat melihat metode yang disebut Kill, yang menyumbang lebih dari 1% waktu kompilasi. Beberapa metode terbaik lainnya juga akan dibahas nanti dalam postingan blog.
Tampilan profil dari bawah ke atas
Di compiler pengoptimalan kami, ada fase yang disebut Global Value Numbering (GVN). Anda tidak perlu mengkhawatirkan fungsinya secara keseluruhan, tetapi bagian yang relevan adalah mengetahui bahwa ia memiliki metode yang disebut `Kill` yang akan menghapus beberapa node sesuai dengan filter. Hal ini memakan waktu karena harus melakukan iterasi melalui semua node dan memeriksa satu per satu. Kami menyadari bahwa ada beberapa kasus di mana kami tahu sebelumnya bahwa pemeriksaan akan salah, terlepas dari node yang aktif pada saat itu. Dalam kasus ini, kita dapat melewati iterasi sama sekali, sehingga menurunkannya dari 1,023% menjadi ~0,3% dan meningkatkan runtime GVN sebesar ~15%.
Menerapkan pengoptimalan yang bermanfaat
Kita telah membahas cara mengukur dan mendeteksi di mana waktu dihabiskan, tetapi ini baru permulaan. Langkah berikutnya adalah cara mengoptimalkan waktu yang dihabiskan untuk mengompilasi.
Biasanya, dalam kasus seperti `Kill` di atas, kita akan melihat cara melakukan iterasi melalui node dan melakukannya lebih cepat, misalnya, dengan melakukan hal-hal secara paralel atau meningkatkan algoritma itu sendiri. Faktanya, itulah yang kami coba lakukan pada awalnya dan hanya ketika kami tidak dapat menemukan apa pun untuk dilakukan, kami mengalami momen “Tunggu sebentar…” dan melihat bahwa solusinya adalah (dalam beberapa kasus) tidak melakukan iterasi sama sekali. Saat melakukan pengoptimalan semacam ini, mudah untuk melewatkan gambaran besarnya.
Dalam kasus lain, kami menggunakan beberapa teknik yang berbeda, termasuk:
- menggunakan heuristik untuk memutuskan apakah pengoptimalan akan gagal menghasilkan hasil yang berharga dan oleh karena itu dapat dilewati
- menggunakan struktur data tambahan untuk meng-cache data yang dikomputasi
- mengubah struktur data saat ini untuk meningkatkan kecepatan
- menghitung hasil secara lambat untuk menghindari siklus dalam beberapa kasus
- menggunakan abstraksi yang tepat - fitur yang tidak perlu dapat memperlambat kode
- menghindari pengejaran penunjuk yang sering digunakan melalui banyak pemuatan
Bagaimana cara mengetahui apakah pengoptimalan tersebut layak dilakukan?
Bagian yang menarik adalah Anda tidak perlu melakukannya. Setelah mendeteksi bahwa suatu area menghabiskan banyak waktu kompilasi dan setelah mencurahkan waktu pengembangan untuk mencoba meningkatkannya, terkadang Anda tidak dapat menemukan solusi. Mungkin tidak ada yang perlu dilakukan, implementasinya akan memakan waktu terlalu lama, akan menurunkan metrik lain secara signifikan, meningkatkan kompleksitas basis kode, dll. Untuk setiap pengoptimalan yang berhasil yang dapat Anda lihat di postingan blog ini, ketahui bahwa ada banyak pengoptimalan lain yang tidak berhasil.
Jika Anda berada dalam situasi serupa, coba perkirakan seberapa besar peningkatan metrik yang akan Anda peroleh dengan melakukan pekerjaan sesedikit mungkin. Artinya, secara berurutan:
- Memperkirakan dengan metrik yang telah Anda kumpulkan, atau hanya berdasarkan intuisi
- Memperkirakan dengan prototipe cepat dan sederhana
- Terapkan solusi.
Jangan lupa untuk mempertimbangkan perkiraan kekurangan solusi Anda. Misalnya, jika Anda akan mengandalkan struktur data tambahan, berapa banyak memori yang ingin Anda gunakan?
Mempelajari lebih dalam
Tanpa berlama-lama, mari kita lihat beberapa perubahan yang kami terapkan.
Kami menerapkan perubahan untuk mengoptimalkan metode yang disebut FindReferenceInfoOf. Metode ini melakukan penelusuran linear vektor untuk menemukan entri. Kami memperbarui struktur data tersebut agar diindeks berdasarkan ID petunjuk sehingga FindReferenceInfoOf akan menjadi O(1, bukan O(n). Selain itu, kami telah mengalokasikan vektor sebelumnya untuk menghindari perubahan ukuran. Kami sedikit meningkatkan memori karena harus menambahkan kolom tambahan yang menghitung jumlah entri yang kami masukkan dalam vektor, tetapi ini adalah pengorbanan kecil karena memori puncak tidak meningkat. Hal ini mempercepat fase LoadStoreAnalysis kami sebesar 34-66% yang pada gilirannya memberikan peningkatan waktu kompilasi sebesar ~0,5-1,8%.
Kami memiliki implementasi HashSet kustom yang kami gunakan di beberapa tempat. Pembuatan struktur data ini membutuhkan waktu yang cukup lama dan kami menemukan alasannya. Bertahun-tahun yang lalu, struktur data ini hanya digunakan di beberapa tempat yang menggunakan HashSet yang sangat besar dan diubah agar dioptimalkan untuk itu. Namun, saat ini, fitur ini digunakan ke arah yang berlawanan dengan hanya beberapa entri dan masa aktif yang singkat. Artinya, kita membuang siklus dengan membuat HashSet yang sangat besar ini, tetapi hanya menggunakannya untuk beberapa entri sebelum menghapusnya. Dengan perubahan ini, kami meningkatkan waktu kompilasi sekitar 1,3-2%. Sebagai bonus tambahan, penggunaan memori berkurang sekitar 0,5-1% karena kami tidak menggunakan struktur data sebesar sebelumnya.
Kami meningkatkan waktu kompilasi sekitar 0,5-1% dengan meneruskan struktur data berdasarkan referensi ke lambda untuk menghindari penyalinannya. Hal ini tidak terdeteksi dalam peninjauan awal dan berada di codebase kami selama bertahun-tahun. Berkat melihat profil di pprof, kami menyadari bahwa metode ini membuat dan menghancurkan banyak struktur data, sehingga kami menyelidiki dan mengoptimalkannya.
Kami mempercepat fase yang menulis output yang dikompilasi dengan meng-cache nilai yang dihitung, yang menghasilkan peningkatan waktu kompilasi total sebesar ~1,3-2,8%. Sayangnya, pembukuan tambahan terlalu banyak dan pengujian otomatis kami memberi tahu kami tentang regresi memori. Kemudian, kami memeriksa kembali kode yang sama dan menerapkan versi baru yang tidak hanya mengatasi regresi memori, tetapi juga meningkatkan waktu kompilasi sebesar ~0,5-1,8% lagi. Pada perubahan kedua ini, kita harus memfaktorkan ulang dan membayangkan kembali cara kerja fase ini, untuk menghilangkan salah satu dari dua struktur data.
Kami memiliki fase dalam compiler pengoptimalan yang menyisipkan panggilan fungsi untuk mendapatkan performa yang lebih baik. Untuk memilih metode mana yang akan di-inline, kita menggunakan heuristik sebelum melakukan komputasi apa pun, dan pemeriksaan akhir setelah melakukan pekerjaan, tetapi tepat sebelum kita menyelesaikan inlining. Jika salah satu di antaranya mendeteksi bahwa inlining tidak sepadan (misalnya, terlalu banyak petunjuk baru yang akan ditambahkan), maka kita tidak akan melakukan inlining panggilan metode.
Kami memindahkan dua pemeriksaan dari kategori “pemeriksaan akhir” ke kategori “heuristik” untuk memperkirakan apakah penyisipan akan berhasil atau tidak sebelum kami melakukan komputasi yang memakan banyak waktu. Karena ini adalah perkiraan, maka tidak sempurna, tetapi kami memverifikasi bahwa heuristik baru kami mencakup 99,9% dari apa yang sebelumnya di-inline tanpa memengaruhi performa. Salah satu heuristik baru ini adalah tentang register DEX yang diperlukan (~0,2-1,3% peningkatan), dan yang lainnya tentang jumlah instruksi (~2% peningkatan).
Kami memiliki penerapan BitVector kustom yang kami gunakan di beberapa tempat. Kami mengganti class BitVector yang dapat diubah ukurannya dengan BitVectorView yang lebih sederhana untuk vektor bit berukuran tetap tertentu. Hal ini menghilangkan beberapa pengalihan dan pemeriksaan rentang runtime serta mempercepat pembuatan objek vektor bit.
Selain itu, class BitVectorView di-templatized pada jenis penyimpanan yang mendasarinya (bukan selalu menggunakan uint32_t seperti BitVector lama). Hal ini memungkinkan beberapa operasi, misalnya Union(), memproses dua kali lebih banyak bit secara bersamaan di platform 64-bit. Sampel fungsi yang terpengaruh berkurang lebih dari 1% secara total saat mengompilasi Android OS. Hal ini dilakukan di beberapa perubahan [1, 2, 3, 4, 5, 6]
Jika kita membahas semua pengoptimalan secara mendetail, kita akan berada di sini sepanjang hari. Jika Anda tertarik dengan pengoptimalan lainnya, lihat beberapa perubahan lain yang kami terapkan:
- Menambahkan pembukuan untuk meningkatkan waktu kompilasi sebesar ~0,6-1,6%.
- Hitung data secara lambat untuk menghindari siklus, jika memungkinkan.
- Faktorkan ulang kode kita untuk melewati tugas pra-komputasi jika tidak akan digunakan.
- Hindari beberapa rantai pemuatan dependen saat pengalokasi dapat diperoleh dengan mudah dari tempat lain.
- Kasus lain terkait menambahkan pemeriksaan untuk menghindari tugas yang tidak perlu.
- Hindari percabangan yang sering pada jenis register (core/FP) di pengalokasi register.
- Pastikan beberapa array diinisialisasi pada waktu kompilasi. Jangan mengandalkan clang untuk melakukannya.
- Bersihkan beberapa loop. Gunakan loop rentang yang dapat dioptimalkan clang dengan lebih baik karena tidak perlu memuat ulang pointer internal penampung karena efek samping loop. Hindari pemanggilan fungsi virtual `HInstruction::GetInputRecords()` dalam loop melalui `InputAt(.)` inline untuk setiap input.
- Hindari fungsi Accept() untuk pola pengunjung dengan mengeksploitasi pengoptimalan compiler.
Kesimpulan
Dedikasi kami untuk meningkatkan kecepatan waktu kompilasi ART telah menghasilkan peningkatan yang signifikan, membuat Android lebih lancar dan efisien sekaligus berkontribusi pada daya tahan baterai dan termal perangkat yang lebih baik. Dengan mengidentifikasi dan menerapkan pengoptimalan secara cermat, kami telah menunjukkan bahwa peningkatan waktu kompilasi yang signifikan dapat dilakukan tanpa mengorbankan penggunaan memori atau kualitas kode.
Perjalanan kami melibatkan pembuatan profil dengan alat seperti pprof, keinginan untuk melakukan iterasi, dan terkadang bahkan mengabaikan jalur yang kurang membuahkan hasil. Upaya kolektif tim ART tidak hanya mengurangi waktu kompilasi dengan persentase yang signifikan, tetapi juga telah meletakkan dasar bagi kemajuan di masa mendatang.
Semua peningkatan ini tersedia dalam update Android akhir tahun 2025, dan untuk Android 12 dan yang lebih baru melalui update utama. Kami harap pembahasan mendalam tentang proses pengoptimalan kami ini memberikan insight berharga tentang kompleksitas dan manfaat teknik compiler.
Lanjutkan membaca
-
Berita Produk
Setiap developer memiliki alur kerja dan kebutuhan AI yang unik, dan penting untuk dapat memilih cara AI membantu pengembangan Anda. Pada bulan Januari, kami memperkenalkan kemampuan untuk memilih model AI lokal atau jarak jauh guna mendukung fungsi AI di Android Studio
Matthew Warner • Waktu baca: 2 menit
-
Berita Produk
Android Studio Panda 3 kini stabil dan siap digunakan dalam produksi. Rilis ini memberi Anda lebih banyak kontrol dan penyesuaian atas alur kerja yang didukung AI, sehingga mempermudah pembuatan aplikasi Android berkualitas tinggi.
Matt Dyor • Waktu baca 3 menit
-
Berita Produk
Di Google, kami berkomitmen untuk menghadirkan model AI tercanggih langsung ke perangkat Android di saku Anda. Hari ini, dengan senang hati kami mengumumkan rilis model terbuka canggih terbaru kami: Gemma 4.
Caren Chang, David Chou • Waktu baca 3 menit
Terus dapatkan informasi
Dapatkan insight pengembangan Android terbaru yang dikirim ke kotak masuk Anda setiap minggu.