Компілятор, Транспайлер, Інтерпретатор та JIT

9 жовтня 2025 р.4 хв читання
Примітка: Я написав цю записку в школі, мотивований потребою пояснити цю тему деяким своїм одноліткам. Не очікуйте від неї багато чого 😄
Компілятор (Compiler) — це програма, яка трансформує вихідний код (source code) у проміжне представлення (Intermediate Representation, IR) або безпосередньо у машинний код (machine code).
Приклади компіляторів, які трансформують вихідний код в IR, включають ті, що націлені на Java або Kotlin. Мови на кшталт C++ зазвичай компілюються безпосередньо в машинний код.

Транспайлер (Transpiler)

Транспайлер також трансформує вихідний код, але замість того, щоб створювати IR або машинний код, він конвертує код з однієї мови високого рівня в іншу. Він трансформує вихідний код у вихідний код іншої мови, покладається на інфраструктуру іншої мови та не надає жодних гарантій, наприклад, щодо збереження семантики між мовами.
Простіше кажучи, це "дурний" (dummy) процес, на кшталт конвертації:
kotlin
fun main() { val a: Int = 0 }
у:
javascript
function main() { const a = 0 }
Однак JS не має семантики функції "main".
Реальним прикладом цього може бути Babel, який конвертує JS одного стандарту в інший. Він просто "адаптує" синтаксис вашого коду, не перевіряючи його на наявність помилок.

Інтерпретатор (Interpreter)

Інтерпретатор — це програма, яка перекладає вихідний код або проміжне представлення (IR) у машинні інструкції на льоту (on-the-fly) під час виконання (runtime), замість виконання перекладу під час компіляції (compile time).
Приклади мов, які працюють таким чином, включають Python та Java.
Але чому Java? Хоча Java зазвичай не вважається інтерпретованою мовою, JVM інтерпретує байт-код (bytecode) JVM на льоту під час першого запуску — а іноді й під час наступних запусків.
Різниця між цими двома полягає в тому, що для деплою (deploy) Python ми надсилаємо вихідний код безпосередньо, тоді як код Java компілюється заздалегідь у байт-код перед деплоєм.
Підсумок
  • З Java ми деплоїмо JVM "IR" (більш відомий як JVM-байт-код).
  • З Python ми деплоїмо вихідний код, який потім виконується інтерпретатором.
  • Однак в обох випадках при першому запуску JVM та інтерпретатор Python виконують програму, тому процеси не є фундаментально різними.
Як Java, так і Python можуть використовувати JIT-компіляцію (наприклад, через GraalVM) на додаток до їх стандартного виконання. Java компілюється перед деплоєм, тоді як Python компілюється при першому запуску (Introduction to Python Bytecode).
Проте існують важливі відмінності: байт-код Python не є стабільним між версіями і не призначений для цього. Його основна мета — полегшити інтерпретацію, спрощуючи вирази, класи та функції.

JIT-компілятор (JIT-Compiler)

JIT-компілятор — це програма, яка працює разом з інтерпретатором, аналізуючи виконання для оптимізації продуктивності. Люди іноді плутають його з повним компілятором; хоча він і виконує компіляцію, це не означає, що вся програма повністю скомпільована. Наприклад, JVM JIT аналізує "гарячі шляхи" (hot paths) програми — частини програми, які виконуються часто — і може застосовувати різні оптимізації, такі як:
  • Dead code elimination: Код, який не використовується, видаляється в runtime.
  • Methods inlining (Інлайнінг методів): Методи, які часто використовуються, є невеликими за розміром і не вводять локів (наприклад, використовуючи ключове слово synchronized), вставляються безпосередньо в місці їх виклику, щоб зменшити кількість інструкцій (для віртуального виклику) до процесора.
  • Branch elimination: Спекулятивна оптимізація, що базується на даних виконання в runtime. Як тільки інтерпретатор або runtime має достатньо інформації про ймовірності переходів (наприклад, якщо гілка виконується рідко або ніколи), він може видалити гілку і припустити, що вона не відбудеться. Це корисно, коли певні стани програми не можуть бути визначені під час компіляції. Зазвичай JIT вставляє гарди (guards) для елімінованих гілок, що дозволяє йому повернутися до інтерпретації (і потенційно перекомпілювати в машинний код), якщо припущення виявиться невірним.
  • Compilation into machine code (Компіляція в машинний код): Як тільки JIT бачить, що якийсь код виконується досить часто, він компілює його в бінарний код. Деякий код може ніколи не бути скомпільованим, наприклад, код, який використовується виключно для бутстрапінгу (bootstrapping).
JIT-компілятор може оптимізувати ваш код так, щоб він працював так само ефективно і швидко, як код на C/C++ — а іноді навіть швидше, завдяки спекулятивним оптимізаціям, що виконуються на льоту, які компілятори C/C++ не можуть застосувати. Однак JIT-компільовані програми зазвичай мають повільніший час запуску (startup time).

Яка різниця між компільованими та інтерпретованими мовами?

Хоча C, C++, Rust і подібні мови є чітко компільованими мовами (безпосередньо транслюються в машинний код), відмінність менш чітка для Java та Python.
Хоча Python вважається інтерпретованою мовою, а Java зазвичай ні, JVM по суті виконує той самий тип виконання, що й інтерпретатор Python. Тому основна різниця полягає в деплої: Java компілюється перед деплоєм і не потребує вихідного коду для виконання, тоді як Python зазвичай виконується з вихідного коду.
Врешті-решт, ми можемо вважати компільованими мовами ті, що компілюються у виконуваний формат перед доставкою. Також варто зазначити, що більшість інтерпретованих мов працюють подібно до Python, покладаючись на інтерпретацію в runtime.