Dart menyediakan dua mesin eksekusi untuk satu bahasa yang sama. Selama pengembangan, dart run mengeksekusi program dengan just-in-time (JIT), yaitu kompilasi yang dilakukan ketika program berjalan. Saat program siap disebarkan, dart compile exe menghasilkan biner ahead-of-time (AOT), yaitu kompilasi yang selesai sebelum program dijalankan. Topik ini membandingkan kedua mesin tersebut pada satu program statistik nilai yang sama, mulai dari siklus kerjanya, keluaran nyata di terminal, waktu mulai, hingga konsekuensi ketika sumber disunting tanpa kompilasi ulang.
Dua mesin eksekusi Dart#
Mesin JIT bekerja sambil mengamati. Mesin virtual membaca sumber program, mengompilasi setiap metode ketika pertama kali dipanggil, lalu memperhatikan bagian yang paling sering dieksekusi untuk mengoptimalkannya lebih lanjut. Siklus menyunting dan menjalankan menjadi cepat karena tidak ada langkah build di antara keduanya, dan VM selalu melihat versi sumber terbaru. Dokumentasi Dart bahkan mencatat bahwa kode hasil JIT dapat mencapai performa puncak yang lebih tinggi daripada AOT apabila data pelatihannya baik, karena optimasi berbasis pengamatan menyesuaikan diri dengan pola penggunaan nyata.
Mesin AOT bekerja sebaliknya. Seluruh program dikompilasi sekali menjadi kode mesin sebelum dijalankan, dan kode tersebut dipaketkan bersama runtime Dart yang kecil dalam satu berkas. Waktu mulai menjadi sangat pendek karena tidak ada pekerjaan kompilasi yang tersisa saat program berjalan, tetapi pengamatan JIT hilang, sehingga optimasinya umumnya lebih konservatif. Biaya kompilasi yang semula dibayar setiap kali program dijalankan kini dibayar sekali di akhir, tepat sebelum program disebarkan.
Program demo: statistik nilai#
Perbandingan dilakukan pada satu program kecil yang sama, yaitu penghitung statistik nilai ujian sepuluh siswa. Program membaca list tetap berisi sepuluh angka, menghitung jumlah, rata-rata, nilai tertinggi, dan nilai terendah, lalu mencetak semuanya. Angka pada daftar sengaja dipilih agar mudah dihitung ulang, sehingga keluaran di setiap bagian dapat diverifikasi tanpa alat apa pun. Program ini tidak bergantung pada package apa pun, sehingga satu berkas saja cukup untuk seluruh percobaan.
Berkas programnya ditampilkan di bawah, dan blok bash sesudahnya menyalin berkas yang sama ke folder contoh. Blok tersebut selalu menghapus folder lama terlebih dahulu agar urutan percobaan dapat diulang dari nol kapan saja. Seluruh perintah pada topik ini bekerja pada folder contoh tersebut, sehingga tidak ada berkas tambahan yang tertinggal di tempat lain.
void main() {
final nilai = [72, 85, 90, 68, 77, 93, 81, 79, 88, 74];
var total = 0;
var tertinggi = nilai.first;
var terendah = nilai.first;
for (final n in nilai) {
total += n;
if (n > tertinggi) tertinggi = n;
if (n < terendah) terendah = n;
}
final rataRata = total / nilai.length;
print('jumlah nilai : ${nilai.length}');
print('total : $total');
print('rata-rata : ${rataRata.toStringAsFixed(2)}');
print('tertinggi : $tertinggi');
print('terendah : $terendah');
}# siapkan folder contoh program statistik nilai
rm -rf ~/contoh/melampaui-mobile/aot-versus-jit/statistik
mkdir -p ~/contoh/melampaui-mobile/aot-versus-jit/statistik
cd ~/contoh/melampaui-mobile/aot-versus-jit/statistik
export PATH=/home/kal/dart-sdk/bin:$PATH
cat > statistik_nilai.dart <<'EOF'
void main() {
final nilai = [72, 85, 90, 68, 77, 93, 81, 79, 88, 74];
var total = 0;
var tertinggi = nilai.first;
var terendah = nilai.first;
for (final n in nilai) {
total += n;
if (n > tertinggi) tertinggi = n;
if (n < terendah) terendah = n;
}
final rataRata = total / nilai.length;
print('jumlah nilai : ${nilai.length}');
print('total : $total');
print('rata-rata : ${rataRata.toStringAsFixed(2)}');
print('tertinggi : $tertinggi');
print('terendah : $terendah');
}
EOFMenjalankan dengan JIT#
Perintah dart run mengeksekusi sumber program langsung pada VM mode JIT. Tidak ada berkas keluaran yang dihasilkan, dan setiap pemanggilan mengulangi seluruh pekerjaan pemuatan serta kompilasinya. Jalankan perintah berikut pada folder contoh, lalu bandingkan angkanya dengan perhitungan manual.
# jalankan program pada VM mode JIT
dart run statistik_nilai.dartjumlah nilai : 10
total : 807
rata-rata : 80.70
tertinggi : 93
terendah : 68Keluaran muncul setelah VM memuat, menguraikan, dan mengompilasi sumber sekaligus. Pada pengukuran di sandbox, proses dari mulai sampai baris pertama memakan waktu sekitar dua ratus milidetik untuk program sekecil ini, dan seluruh biaya itu dibayar ulang setiap kali perintah dipanggil. Di sisi lain, tidak ada artefak yang perlu dirawat, dan setiap penyuntingan pada sumber langsung terlihat pada eksekusi berikutnya. Kombinasi itulah yang menjadikan dart run sebagai perintah sehari-hari selama pengembangan, sedangkan debugger dan profiler Dart DevTools juga bekerja penuh pada mode ini.
Kompilasi AOT dengan dart compile exe#
Perintah dart compile exe mengompilasi seluruh program menjadi kode mesin dan mengemasnya bersama runtime ke sebuah biner mandiri.
# kompilasi menjadi biner mandiri
dart compile exe statistik_nilai.dartGenerated: /home/kal/contoh/melampaui-mobile/aot-versus-jit/statistik/statistik_nilai.exePesan Generated: menunjuk berkas biner yang baru dibuat beserta lokasinya. Pada sandbox, kompilasi program sekecil ini selesai dalam waktu sekitar satu detik, sedangkan program yang lebih besar memerlukan waktu yang lebih lama pula. Ukuran binernya sekitar 6 MB untuk program sekecil ini, jauh melebihi ukuran sumbernya, karena runtime Dart dan kode hasil kompilasi ikut dikemas di dalamnya. Berkas itu berdiri sendiri, sehingga mesin target tidak memerlukan Dart SDK, dan pada sandbox binernya dikenali sebagai berkas ELF 64-bit untuk arsitektur x86-64.
Biner yang sudah dibuat dijalankan langsung tanpa perintah dart run.
# jalankan biner hasil kompilasi AOT
./statistik_nilai.exejumlah nilai : 10
total : 807
rata-rata : 80.70
tertinggi : 93
terendah : 68Keluarannya identik dengan versi JIT karena keduanya menjalankan logika yang sama. Perbedaannya berada di waktu mulai, yaitu biner AOT mulai mencetak baris pertamanya dalam beberapa milidetik, sedangkan dart run memerlukan ratusan milidetik untuk memuat VM dan mengompilasi sumber. Keuntungan lain adalah kemandirian, karena biner dapat disalin ke mesin lain yang setara arsitekturnya tanpa membawa SDK. Biaya yang dibayar adalah langkah kompilasi yang harus diulang setiap kali sumber berubah, dan kelalaian mengulangnya justru menjadi kesalahan yang sering terjadi, seperti ditunjukkan pada bagian Kesalahan umum.
Memilih mode sesuai keperluan#
Tabel berikut merangkum perbedaan yang baru saja diamati pada program yang sama.
| Aspek | JIT dengan dart run | AOT dengan dart compile exe |
|---|---|---|
| Langkah sebelum berjalan | tidak ada | satu kali kompilasi, sekitar satu detik untuk program kecil |
| Waktu mulai | ratusan milidetik | beberapa milidetik |
| Artefak | tidak ada berkas baru | biner mandiri sekitar 6 MB |
| Kebutuhan mesin target | Dart SDK | tidak ada |
| Reaksi terhadap perubahan sumber | langsung terlihat saat dijalankan ulang | perlu kompilasi ulang |
dart compile juga menyediakan bentuk keluaran lain selain kedua bentuk utama di atas. Subcommand aot-snapshot menghasilkan modul AOT tanpa runtime yang dijalankan bersama dartaotruntime, dan bentuk ini berguna untuk menghemat ruang ketika menyebarkan beberapa aplikasi sekaligus. Subcommand jit-snapshot menyimpan hasil kompilasi dari satu pelatihan agar VM tidak perlu mengompilasi ulang bagian yang sama pada eksekusi berikutnya, sedangkan kernel mengemas program menjadi modul portabel lintas sistem operasi dan arsitektur. Target web pun tersedia, yaitu js dan wasm, yang menghasilkan keluaran berupa berkas JavaScript dan WebAssembly dengan kebutuhan pemuatan yang berbeda dari target native.
Kesalahan umum: biner tidak ikut berubah#
Kesalahan yang mudah terjadi adalah menyunting sumber lalu menjalankan biner lama karena lupa kompilasi ulang. Biner AOT memuat hasil kompilasi pada saat dibuat, dan perubahan apa pun pada sumber setelah saat itu tidak pernah tampak pada biner lama. Demo berikut mengubah nilai pertama pada daftar dari 72 menjadi 82, menjalankan program dengan JIT, lalu menjalankan biner yang belum di-compile ulang.
# ubah nilai pertama pada sumber menjadi 82
cat > statistik_nilai.dart <<'EOF'
void main() {
final nilai = [82, 85, 90, 68, 77, 93, 81, 79, 88, 74];
var total = 0;
var tertinggi = nilai.first;
var terendah = nilai.first;
for (final n in nilai) {
total += n;
if (n > tertinggi) tertinggi = n;
if (n < terendah) terendah = n;
}
final rataRata = total / nilai.length;
print('jumlah nilai : ${nilai.length}');
print('total : $total');
print('rata-rata : ${rataRata.toStringAsFixed(2)}');
print('tertinggi : $tertinggi');
print('terendah : $terendah');
}
EOF
# jalankan dengan JIT: hasil baru langsung terlihat
dart run statistik_nilai.dart
# biner AOT masih memuat kompilasi lama
./statistik_nilai.exejumlah nilai : 10
total : 817
rata-rata : 81.70
tertinggi : 93
terendah : 68
jumlah nilai : 10
total : 807
rata-rata : 80.70
tertinggi : 93
terendah : 68Dua keluaran dalam satu blok bersebelahan dan saling bertentangan. Perintah dart run melaporkan total 817 sesuai sumber yang baru, sedangkan biner masih melaporkan 807 dari kompilasi lama. Tidak ada peringatan apa pun yang menyampaikan ketidaksesuaian tersebut, karena menurut biner semuanya berjalan sebagaimana mestinya, dan hanya pembacaan ulang sumber yang mengungkap perbedaannya.
Kompilasi ulang menghilangkan perbedaan itu.
# kompilasi ulang agar biner mengikuti sumber yang baru
dart compile exe statistik_nilai.dart
./statistik_nilai.exeGenerated: /home/kal/contoh/melampaui-mobile/aot-versus-jit/statistik/statistik_nilai.exe
jumlah nilai : 10
total : 817
rata-rata : 81.70
tertinggi : 93
terendah : 68Aturan kerjanya sederhana, yaitu setiap kali sumber berubah dan hasil AOT diinginkan, dart compile exe dijalankan kembali sebelum biner digunakan atau disebarkan. Kebiasaan membandingkan keluaran biner dengan keluaran dart run setelah setiap kompilasi, seperti yang dilakukan di atas, menjadi pemeriksaan cepat yang menangkap kelalaian ini. Pemeriksaan semacam itu juga mudah dimasukkan ke skrip rilis, sehingga biner yang disebar selalu dibuat dari sumber terbaru.
Ringkasan#
- JIT mengeksekusi sumber langsung ketika program berjalan, sehingga siklus menyunting dan menjalankan berlangsung tanpa langkah build, tetapi waktu mulainya mencapai ratusan milidetik.
- AOT mengompilasi seluruh program menjadi biner mandiri yang runtime-nya ikut dikemas, waktu mulainya hanya beberapa milidetik, dan mesin target tidak memerlukan Dart SDK.
- Biner AOT spesifik sistem operasi dan arsitektur, berukuran sekitar 6 MB untuk program kecil, dan namanya selalu diakhiri
.exepada semua sistem. - Biner AOT memuat hasil kompilasi dari saat dibuat, sehingga perubahan sumber tidak pernah mengubah biner lama sampai kompilasi ulang dijalankan.
Referensi#
- dart compile — dart.dev — Dokumentasi resmi subcommand exe, aot-snapshot, jit-snapshot, kernel, js, dan wasm beserta opsinya.
- dart run — dart.dev — Dokumentasi resmi perintah yang menjalankan program Dart pada VM mode JIT.
- The Dart command-line tool — dart.dev — Gambaran perintah baris perintah Dart yang menyatukan run dan compile dalam satu alat.