Bagaimana kompiler JIT berbeda dari kompiler biasa?

22

Sudah ada banyak hype tentang kompiler JIT untuk bahasa seperti Java, Ruby, dan Python. Bagaimana kompiler JIT berbeda dari kompiler C / C ++, dan mengapa kompiler ditulis untuk Java, Ruby atau Python disebut kompiler JIT, sedangkan kompiler C / C ++ hanya disebut kompiler?

Ken Li
sumber

Jawaban:

17

Kompiler JIT mengkompilasi kode dengan cepat, tepat sebelum eksekusi atau bahkan ketika mereka sudah mengeksekusi. Dengan cara ini, VM tempat kode berjalan dapat memeriksa pola dalam eksekusi kode untuk memungkinkan optimasi yang hanya mungkin dengan informasi run-time. Lebih lanjut, jika VM memutuskan bahwa versi yang dikompilasi tidak cukup baik untuk alasan apa pun (misalnya, terlalu banyak cache yang terlewat, atau kode yang sering melempar pengecualian tertentu), ia mungkin memutuskan untuk mengkompilasi ulangnya dengan cara yang berbeda, yang mengarah ke cara yang jauh lebih cerdas kompilasi.

Di sisi lain, kompiler C dan C ++ secara tradisional bukan JIT. Mereka mengkompilasi dalam sekali tembakan hanya sekali pada mesin pengembang dan kemudian dieksekusi.

Victor Stafusa
sumber
Apakah ada kompiler JIT yang melacak kesalahan cache, dan menyesuaikan strategi kompilasi mereka? @ Viktor
Martin Berger
@ MartinBerger Saya tidak tahu apakah ada kompiler JIT yang tersedia melakukan itu tetapi mengapa tidak? Ketika komputer lebih kuat dan ilmu komputer berkembang, orang dapat menerapkan lebih banyak optimasi ke program. Misalnya ketika Java lahir, itu sangat lambat, mungkin 20 kali dibandingkan dengan yang dikompilasi, tetapi sekarang kinerjanya tidak terlalu banyak dan mungkin dapat dibandingkan dengan beberapa kompiler
phuclv
Saya pernah mendengar ini di beberapa posting blog sejak lama. Kompilator dapat dikompilasi secara berbeda tergantung pada CPU saat ini. Misalnya ia dapat membuat vektor beberapa kode secara otomatis jika mendeteksi bahwa CPU mendukung SSE / AVX. Ketika ekstensi SIMD yang lebih baru tersedia, ekstensi itu dapat dikompilasi ke yang lebih baru sehingga programmer tidak perlu mengubah apa pun. Atau jika dapat mengatur operasi / data untuk mengambil keuntungan dari cache yang lebih besar atau untuk mengurangi miss cache
phuclv
@ LưuVĩnhPhúc Chào! Saya senang untuk mempercayainya, tetapi perbedaannya adalah mis. Tipe CPU atau ukuran cache adalah sesuatu yang statis yang tidak berubah sepanjang perhitungan, jadi bisa dengan mudah dilakukan oleh kompiler statis juga. Tembolok merindukan OTOH sangat dinamis.
Martin Berger
12

JIT adalah kependekan dari just-in-time compiler, dan namanya misson: selama runtime, ia menentukan optimisasi kode yang berharga dan menerapkannya. Itu tidak menggantikan kompiler biasa tetapi merupakan bagian dari penerjemah. Perhatikan bahwa bahasa seperti Java yang menggunakan kode perantara memiliki keduanya : kompiler normal untuk terjemahan kode sumber ke perantara, dan JIT yang disertakan dalam juru bahasa untuk peningkatan kinerja.

Optimalisasi kode tentu saja dapat dilakukan oleh kompiler "klasik", tetapi perhatikan perbedaan utama: kompiler JIT memiliki akses ke data saat runtime. Ini adalah keuntungan besar ; mengeksploitasinya dengan benar mungkin sulit, jelas.

Pertimbangkan, misalnya, kode seperti ini:

m(a : String, b : String, k : Int) {
  val c : Int;
  switch (k) {
    case 0 : { c = 7; break; }
    ...
    case 17 : { c = complicatedMethod(k, a+b); break; }
  }

  return a.length + b.length - c + 2*k;
}

Kompiler normal tidak dapat melakukan terlalu banyak tentang hal ini. Kompiler JIT, bagaimanapun, dapat mendeteksi bahwa mhanya pernah dipanggil dengan k==0beberapa alasan (hal-hal seperti itu dapat terjadi karena perubahan kode dari waktu ke waktu); kemudian dapat membuat versi kode yang lebih kecil (dan mengkompilasinya ke kode asli, meskipun saya menganggap ini sebagai hal kecil, secara konsep):

m(a : String, b : String) {
  return a.length + b.length - 7;
}

Pada titik ini, bahkan mungkin akan sebaris panggilan metode karena sepele sekarang.

Rupanya, Matahari menolak sebagian besar optimisasi yang javacbiasa dilakukan di Jawa 6; Saya telah diberitahu bahwa optimisasi tersebut menyulitkan JIT untuk melakukan banyak hal, dan kode yang dikompilasi secara naif berjalan lebih cepat pada akhirnya. Sosok pergi.

Raphael
sumber
Ngomong-ngomong, dengan adanya penghapusan tipe (misalnya obat generik di Jawa) JIT sebenarnya berada pada posisi yang tidak menguntungkan.
Raphael
Jadi runtime lebih rumit dari itu untuk bahasa yang dikompilasi karena lingkungan runtime harus mengumpulkan data untuk mengoptimalkan.
saadtaame
2
Dalam banyak kasus, JIT tidak akan dapat menggantikan mdengan versi yang tidak memeriksa kkarena tidak akan dapat membuktikan bahwa tidakm akan pernah dipanggil dengan yang bukan nol k, tetapi bahkan tanpa dapat membuktikan bahwa itu dapat menggantikan dengan static miss_count; if (k==0) return a.length+b.length-7; else if (miss_count++ < 16) { ... unoptimized code for m ...} else { ... consider other optimizations...}.
supercat
1
Saya setuju dengan @supercat dan berpikir contoh Anda sebenarnya cukup menyesatkan. Hanya jika k=0 selalu , yang berarti itu bukan fungsi input atau lingkungan, apakah aman untuk membatalkan tes - tetapi menentukan ini memerlukan analisis statis, yang sangat mahal dan dengan demikian hanya terjangkau pada waktu kompilasi. JIT dapat menang ketika satu jalur melalui blok kode diambil lebih sering daripada yang lain, dan versi kode khusus untuk jalur ini akan jauh lebih cepat. Tetapi JIT masih akan memancarkan tes untuk memeriksa apakah jalur cepat berlaku , dan mengambil "jalur lambat" jika tidak.
j_random_hacker
Contoh: Suatu fungsi mengambil Base* pparameter, dan memanggil fungsi virtual melaluinya; Analisis runtime menunjukkan bahwa objek aktual yang menunjuk selalu (atau hampir selalu) tampaknya Derived1bertipe. JIT dapat menghasilkan versi baru dari fungsi dengan pemanggilan Derived1metode yang diselesaikan secara statis (atau bahkan inline) . Kode ini akan didahului oleh kondisional yang memeriksa apakah ppointer vtable menunjuk ke Derived1tabel yang diharapkan ; jika tidak, ia akan melompat ke versi asli fungsi dengan panggilan metode yang lebih lambat diselesaikan secara dinamis.
j_random_hacker