Чим компілятор JIT відрізняється від звичайного компілятора?


22

Було багато шумів щодо компіляторів JIT для таких мов, як Java, Ruby та Python. Чим компілятори JIT відрізняються від компіляторів C / C ++, і чому компілятори, написані для Java, Ruby або Python, називаються компіляторами JIT, тоді як компілятори C / C ++ називаються компіляторами?

Відповіді:


17

Компілятори JIT збирають код на льоту, безпосередньо перед їх виконанням або навіть коли вони вже виконуються. Таким чином, ВМ, де працює код, може перевірити наявність шаблонів у виконанні коду, щоб дозволити оптимізацію, яка була б можлива лише за допомогою інформації про час виконання. Крім того, якщо ВМ вирішить, що компільована версія не є достатньо хорошою з будь-якої причини (наприклад, занадто багато пропущених кеш-пам'яті або код, який часто кидає певний виняток), він може вирішити перекомпілювати його по-іншому, що призводить до набагато розумнішої складання.

З іншого боку, компілятори C і C ++ традиційно не є JIT. Вони компілюються в один кадр лише один раз на машині розробника, після чого виробляється виконуваний файл.


Чи є компілятори JIT, які відслідковують пропуски кешу і відповідно адаптують свою стратегію компіляції? @Victor
Мартін Бергер

@MartinBerger Я не знаю, чи є якісь доступні компілятори JIT так, але чому б ні? Оскільки комп'ютери є більш потужними та розвивається інформатика, люди можуть застосувати більше програм до оптимізації. Наприклад, коли народилася Java, вона дуже повільна, можливо, у 20 разів порівняно зі складеними, але зараз продуктивність не дуже відстає і може бути порівнянна з деякими компіляторами
phuclv

Я чув це про деякі повідомлення в блозі давно. Компілятор може компілюватися по-різному, залежно від поточного процесора. Наприклад, він може автоматично векторизувати деякий код, якщо виявить, що процесор підтримує SSE / AVX. Якщо доступні нові розширення SIMD, вони можуть компілюватися в новіші, тому програмісту нічого не потрібно змінювати. Або якщо він може організувати операції / дані, щоб скористатися більшим кешем або зменшити
пропуск

@ LưuVĩnhPhúc Chào! Я радий цьому вірити, але різниця полягає в тому, що, наприклад, тип процесора або розмір кешу - це щось статичне, яке не змінюється протягом усіх обчислень, тому його може легко зробити і статичний компілятор. Кеш-пропуски OTOH дуже динамічні.
Мартін Бергер

12

JIT - це короткий термін для компілятора, який працює за часом, а ім'я - misson: під час виконання він визначає корисні оптимізації коду та застосовує їх. Він не замінює звичайні компілятори, але є частиною перекладачів. Зауважте, що такі мови, як Java, які використовують проміжний код, мають обидві звичайний компілятор для перекладу вихідного в проміжний код, так і JIT, включений в інтерпретатор для підвищення продуктивності.

Оптимізацію коду, безумовно, можуть виконувати "класичні" компілятори, але зауважте головну відмінність: компілятори JIT мають доступ до даних під час виконання. Це величезна перевага; Очевидно, що його правильно використовувати.

Розглянемо, наприклад, такий код:

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;
}

Звичайний компілятор не може з цим робити занадто багато. Компілятор JIT, однак, може виявити, що mз k==0будь-якої причини викликається лише колись (подібні речі можуть траплятися з часом зміни коду); Потім він може створити меншу версію коду (і скомпілювати його до рідного коду, хоча концептуально я вважаю це другорядним моментом):

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

На цьому етапі, ймовірно, навіть вбудується виклик методу, як це тривіально зараз.

Мабуть, Sun відхилив більшість оптимізацій, які javacраніше робили в Java 6; Мені сказали, що ці оптимізації ускладнювали JIT багато, і наївно складений код пробіг швидше. Піди розберися.


До речі, за наявності стирання типу (наприклад, дженерики на Java) JIT насправді знаходиться в невигідних типах wrt.
Рафаель

Отже, час виконання є складнішим, ніж для компільованої мови, оскільки середовище виконання повинне збирати дані для оптимізації.
saadtaame

2
У багатьох випадках JIT не зможе замінити mверсією , яка не перевіряв , kтак як він не зможе довести , що mНЕ буде ніколи бути викликана з ненульовим k, але навіть не будучи в змозі довести , що він може замінити воно з static miss_count; if (k==0) return a.length+b.length-7; else if (miss_count++ < 16) { ... unoptimized code for м ...} else { ... consider other optimizations...}.
supercat

1
Я погоджуюся з @supercat і думаю, що ваш приклад насправді дуже вводить в оману. Тільки якщо k=0 завжди , це означає, що це не функція вхідних даних або середовища, безпечно відмовитися від тесту, але для визначення цього потрібен статичний аналіз, який дуже дорогий і, таким чином, доступний лише під час компіляції. JIT може виграти, коли один шлях через блок коду береться набагато частіше, ніж інші, і версія коду, спеціалізована для цього шляху, буде набагато швидшою. Але JIT все одно випробовує тест, щоб перевірити, чи застосовується швидкий шлях , і прийме "повільний шлях", якщо ні.
j_random_hacker

Приклад: Функція приймає Base* pпараметр і викликає через нього віртуальні функції; Аналіз виконання показує, що фактичний об’єкт, на який вказували завжди (або майже завжди), схоже, є Derived1типовим. JIT може створити нову версію функції зі статично вирішеними (або навіть вбудованими) викликами Derived1методів. Цьому коду передував би умовний, який перевіряє, чи pvtable pointer вказує на очікувану Derived1таблицю; якщо ні, він замість цього переходить до оригінальної версії функції з її повільнішими динамічно вирішеними методами викликів.
j_random_hacker
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.