Memahami perbedaan: juru bahasa tradisional, JIT compiler, juru JIT dan AOT compiler

130

Saya mencoba memahami perbedaan antara penerjemah tradisional, kompiler JIT, interpreter JIT dan kompiler AOT.

Seorang juru bahasa hanyalah sebuah mesin (virtual atau fisik) yang menjalankan instruksi dalam beberapa bahasa komputer. Dalam pengertian itu, JVM adalah juru bahasa dan CPU fisik adalah juru bahasa.

Kompilasi Ahead-of-Time hanya berarti mengkompilasi kode ke beberapa bahasa sebelum mengeksekusi (menafsirkan) itu.

Namun saya tidak yakin tentang definisi yang tepat dari kompiler JIT dan penerjemah JIT.

Menurut definisi saya baca, JIT kompilasi hanya kompilasi kode hanya sebelum menafsirkan itu.

Jadi pada dasarnya, kompilasi JIT adalah kompilasi AOT, dilakukan tepat sebelum eksekusi (interpretasi)?

Dan juru JIT, adalah program yang berisi kompiler JIT dan juru bahasa, dan mengkompilasi kode (JIT) sebelum ia mengartikannya?

Tolong jelaskan perbedaannya.

Aviv Cohn
sumber
4
Mengapa membuat Anda percaya ada perbedaan antara "JIT compiler" dan "JIT interpreter"? Mereka pada dasarnya dua kata berbeda untuk hal yang sama. Konsep keseluruhan JIT adalah sama, tetapi ada berbagai macam teknik implementasi yang tidak dapat dengan mudah dibagi menjadi "kompiler" vs "juru bahasa".
Greg Hewgill
2
Baca wikipages pada kompilasi Just In Time , AOT compiler , Compiler , Interpreter , bytecode dan juga buku Queinnec Lisp in Small Pieces
Basile Starynkevitch

Jawaban:

198

Gambaran

Sebuah interpreter untuk bahasa X adalah sebuah program (atau mesin, atau hanya semacam mekanisme pada umumnya) yang mengeksekusi program p ditulis dalam bahasa X sehingga ia melakukan efek dan mengevaluasi hasil seperti yang ditentukan oleh spesifikasi X . CPU biasanya merupakan penterjemah untuk set instruksi masing-masing, meskipun CPU workstation modern berkinerja tinggi sebenarnya lebih kompleks dari itu; mereka mungkin benar-benar memiliki set instruksi privat eksklusif yang mendasarinya dan menerjemahkan (kompilasi) atau menafsirkan set instruksi publik yang terlihat secara eksternal.

Sebuah compiler dari X ke Y adalah program (atau mesin, atau hanya semacam mekanisme pada umumnya) yang menerjemahkan program p dari beberapa bahasa X ke dalam program semantik setara p ' dalam beberapa bahasa Y sedemikian rupa bahwa semantik program yang diawetkan, yaitu bahwa menafsirkan p ' dengan seorang juru bahasa untuk Y akan menghasilkan hasil yang sama dan memiliki efek yang sama seperti menafsirkan p dengan seorang juru bahasa untuk X . (Perhatikan bahwa X dan Y mungkin bahasa yang sama.)

Istilah Ahead-of-Time (AOT) dan Just-in-Time (JIT) merujuk pada saat kompilasi terjadi: "waktu" yang dimaksud dalam istilah itu adalah "runtime", yaitu kompiler JIT mengkompilasi program sebagaimana adanya berjalan , kompiler AOT mengkompilasi program sebelum dijalankan . Perhatikan bahwa ini mensyaratkan bahwa kompiler JIT dari bahasa X ke bahasa Y harus entah bagaimana bekerja bersama dengan penerjemah untuk bahasa Y, jika tidak, tidak akan ada cara untuk menjalankan program. (Jadi, misalnya, kompiler JIT yang mengkompilasi JavaScript ke kode mesin x86 tidak masuk akal tanpa CPU x86; ia mengkompilasi program saat sedang berjalan, tetapi tanpa CPU x86 program tidak akan berjalan.)

Perhatikan bahwa perbedaan ini tidak masuk akal bagi penerjemah: seorang penerjemah menjalankan program. Gagasan juru bahasa AOT yang menjalankan program sebelum dijalankan atau juru JIT yang menjalankan program saat sedang berjalan adalah tidak masuk akal.

Jadi kita punya:

  • Kompiler AOT: kompilasi sebelum dijalankan
  • Kompiler JIT: mengkompilasi saat berjalan
  • penerjemah: menjalankan

Kompiler JIT

Di dalam keluarga kompiler JIT, masih ada banyak perbedaan mengenai kapan tepatnya mereka mengkompilasi, seberapa sering , dan pada granularitas apa.

Kompiler JIT di CLR Microsoft misalnya hanya mengkompilasi kode satu kali (saat dimuat) dan mengkompilasi seluruh unit sekaligus. Kompiler lain dapat mengumpulkan informasi saat program sedang berjalan dan mengkompilasi ulang kode beberapa kali ketika informasi baru tersedia yang memungkinkan mereka untuk mengoptimalkannya dengan lebih baik. Beberapa kompiler JIT bahkan mampu menonaktifkan kode. Sekarang, Anda mungkin bertanya pada diri sendiri mengapa seseorang ingin melakukan itu? De-optimisasi memungkinkan Anda untuk melakukan optimasi yang sangat agresif yang mungkin sebenarnya tidak aman: jika ternyata Anda terlalu agresif, Anda bisa mundur lagi, sedangkan, dengan kompiler JIT yang tidak dapat menonaktifkan, Anda tidak bisa menjalankan optimasi agresif di tempat pertama.

Kompiler JIT dapat mengkompilasi beberapa unit kode statis dalam sekali jalan (satu modul, satu kelas, satu fungsi, satu metode, ...; ini biasanya disebut metode-at-a-waktu JIT, misalnya) atau mereka dapat melacak dinamika eksekusi kode untuk menemukan jejak dinamis (biasanya loop) yang kemudian akan dikompilasi (ini disebut pelacakan JIT).

Menggabungkan Penerjemah dan Penyusun

Juru bahasa dan kompiler dapat digabungkan menjadi mesin eksekusi satu bahasa. Ada dua skenario khas di mana ini dilakukan.

Menggabungkan compiler AOT dari X ke Y dengan seorang juru bahasa untuk Y . Di sini, biasanya X adalah beberapa bahasa tingkat tinggi yang dioptimalkan untuk dibaca oleh manusia, sedangkan Yadalah bahasa yang ringkas (seringkali semacam bytecode) yang dioptimalkan untuk ditafsirkan oleh mesin. Sebagai contoh, mesin eksekusi CPython Python memiliki kompiler AOT yang mengkompilasi kode sumber Python ke bytecode CPython dan seorang juru bahasa yang mengartikan bytecode CPython. Demikian juga, mesin eksekusi YARV Ruby memiliki kompiler AOT yang mengkompilasi kode sumber Ruby ke bytecode YARV dan seorang penerjemah yang menginterpretasikan bytecode YARV. Mengapa Anda ingin melakukan itu? Ruby dan Python keduanya sangat tinggi tingkat dan bahasa agak rumit, jadi kami pertama kompilasi mereka ke dalam bahasa yang lebih mudah untuk mengurai dan lebih mudah untuk menafsirkan, dan kemudian menafsirkan bahwa bahasa.

Cara lain untuk menggabungkan interpreter dan kompiler adalah mesin eksekusi mode campuran . Di sini, kita "campuran" dua "mode" pelaksanaan yang sama bahasa bersama-sama, yaitu seorang juru bahasa untuk X dan compiler JIT dari X ke Y . (Jadi, perbedaannya di sini adalah bahwa dalam kasus di atas, kami memiliki beberapa "tahap" dengan kompiler yang menyusun program dan kemudian memasukkan hasilnya ke penerjemah, di sini kami memiliki dua yang bekerja berdampingan pada bahasa yang sama. ) Kode yang telah dikompilasi oleh kompiler cenderung berjalan lebih cepat daripada kode yang dieksekusi oleh seorang juru bahasa, tetapi sebenarnya mengkompilasi kode terlebih dahulu membutuhkan waktu (dan khususnya, jika Anda ingin mengoptimalkan kode untuk dijalankan.sangat cepat, butuh banyak waktu). Jadi, untuk menjembatani saat ini di mana kompiler JIT sedang sibuk menyusun kode, penerjemah sudah dapat mulai menjalankan kode, dan setelah JIT selesai dikompilasi, kita dapat mengalihkan eksekusi ke kode yang dikompilasi. Ini berarti bahwa kami mendapatkan performa terbaik dari kode yang dikompilasi, tetapi kami tidak harus menunggu kompilasi selesai, dan aplikasi kami mulai berjalan langsung (walaupun tidak secepat mungkin).

Ini sebenarnya hanya aplikasi yang paling sederhana dari mesin eksekusi mode campuran. Kemungkinan yang lebih menarik adalah, misalnya, untuk tidak segera mulai mengkompilasi, tetapi biarkan penerjemah berjalan sedikit, dan kumpulkan statistik, informasi profil, ketik informasi, informasi tentang kemungkinan cabang kondisional tertentu diambil, metode apa yang disebut paling sering dll. dan kemudian mengumpankan informasi dinamis ini ke kompiler sehingga dapat menghasilkan kode yang lebih optimal. Ini juga merupakan cara untuk mengimplementasikan de-optimasi yang saya bicarakan di atas: jika ternyata Anda terlalu agresif dalam mengoptimalkan, Anda dapat membuang (bagian dari) kode dan kembali ke menafsirkan. HotSpot JVM melakukan ini, misalnya. Ini berisi interpreter untuk bytecode JVM dan juga kompiler untuk bytecode JVM. (Faktanya,dua kompiler!)

Hal ini juga mungkin dan bahkan umum untuk menggabungkan dua pendekatan: dua fase dengan yang pertama menjadi compiler AOT yang mengkompilasi X untuk Y dan tahap kedua menjadi mesin campuran-mode yang baik menafsirkan Y dan mengkompilasi Y ke Z . Mesin eksekusi Rubinius Ruby bekerja dengan cara ini, misalnya: ia memiliki kompiler AOT yang mengkompilasi kode sumber Ruby ke bytecode Rubinius dan mesin mode campuran yang pertama menginterpretasikan bytecode Rubinius dan setelah ia mengumpulkan beberapa informasi, kompilasi informasi yang paling sering disebut metode menjadi asli. kode mesin.

Perhatikan bahwa peran yang dimainkan oleh penerjemah dalam kasus mesin eksekusi mode campuran, yaitu menyediakan startup cepat, dan juga berpotensi mengumpulkan informasi dan menyediakan kemampuan mundur dapat juga dimainkan oleh kompiler JIT kedua. Inilah cara kerja V8, misalnya. V8 tidak pernah mengartikan, selalu mengkompilasi. Kompiler pertama adalah kompiler yang sangat cepat, sangat ramping yang dijalankan dengan sangat cepat. Kode yang dihasilkannya tidak terlalu cepat. Kompiler ini juga menyuntikkan kode profil ke dalam kode yang dihasilkannya. Kompiler lain lebih lambat dan menggunakan lebih banyak memori, tetapi menghasilkan kode lebih cepat, dan dapat menggunakan informasi profil yang dikumpulkan dengan menjalankan kode yang dikompilasi oleh kompiler pertama.

Jörg W Mittag
sumber
1
Apakah kompiler bytecode Python dan Ruby benar-benar dihitung sebagai AOT? Mengingat bahwa kedua bahasa memungkinkan memuat modul secara dinamis, yang dikompilasi saat mereka dimuat, mereka berjalan selama runtime program.
Sebastian Redl
1
@SebastianRedl, dengan CPython Anda dapat menjalankan python -m compileall .atau memuat modul satu kali. Bahkan dalam kasus terakhir, karena file tetap dan digunakan kembali setelah dijalankan pertama kali, sepertinya memang AOT.
Paul Draper
Apakah Anda memiliki referensi untuk bacaan lebih lanjut? Saya ingin tahu lebih banyak tentang V8.
Vince Panuccio
@VincePanuccio Posting referensi kompiler Codegen Lengkap dan Crankshaft yang sejak itu telah diganti . Anda dapat menemukannya di internet.
eush77
CLR Jit mengkompilasi metode dengan metode, bukan seluruh perakitan
Grigory