Компилятор, интерпретатор и JIT
Три способа исполнить код: перевести заранее, читать на лету или компилировать прямо во время работы.
Интерпретатор — программа, которая читает и сразу выполняет исходный код инструкция за инструкцией, без отдельного этапа порождения машинного файла.
Есть три классических подхода к тому, как «оживить» текст программы. Они не взаимоисключающие — современные языки часто комбинируют их.
Компилятор: перевести всё заранее
Компилятор (как в C, C++, Rust, Go) проходит по всему исходнику до запуска, порождает машинный код и кладёт его в исполнимый файл. Плюсы: быстрое выполнение, ошибки синтаксиса находятся до старта. Минусы: нужен отдельный шаг сборки, артефакт привязан к платформе.
Интерпретатор: читать на лету
Интерпретатор (классический Bash, ранние реализации языков) читает программу и тут же выполняет каждую конструкцию. Плюсы: запуск без сборки, переносимость. Минусы: медленнее, ошибки в редких ветках всплывают только при их выполнении.
x = 10
if x > 5:
print("больше пяти")
else:
print("не больше")Вывод:
больше пяти
Байткод: золотая середина
Многие языки (Python, Java, C#) идут средним путём. Сначала компилятор переводит исходник в байткод — компактные инструкции для воображаемой виртуальной машины, не привязанные к конкретному процессору. Потом виртуальная машина исполняет этот байткод. У Python это видно напрямую через модуль dis: вызов dis.dis(add) для функции сложения печатает её байткод. Точный вид зависит от версии Python, но структура такая.
def add(a, b):
return a + b
# байткод (упрощённо):
LOAD_FAST a ; положить a на стек
LOAD_FAST b ; положить b на стек
BINARY_ADD ; сложить два верхних значения
RETURN_VALUE ; вернуть результатJIT: компиляция во время работы
JIT (Just-In-Time) — компиляция «горячих» участков байткода в машинный код прямо во время выполнения, чтобы ускорить часто повторяемый код.
Виртуальная машина замечает, что какой-то метод вызывается тысячи раз, и на ходу компилирует его в нативный машинный код. Так работают JVM (HotSpot), V8 в браузере, PyPy. Это сочетает гибкость интерпретатора со скоростью компилятора.
Как работает под капотом
КОМПИЛЯТОР: исходник --(заранее)--> машинный код --> запуск
ИНТЕРПРЕТАТОР: исходник --(на лету)--> сразу выполняется
БАЙТКОД-ВМ: исходник --> байткод --> ВМ исполняет байткод
JIT: байткод --> ВМ --> горячий код --(на ходу)--> машинный кодЧастые ошибки
Распространённый миф — «Python интерпретируемый, а Java компилируемая». На деле оба сначала компилируются в байткод, а потом исполняются виртуальной машиной. Граница между «компилируемым» и «интерпретируемым» давно размыта.
Ещё ошибка — думать, что JIT всегда быстрее. Для коротких программ накладные расходы на компиляцию не окупаются, и JIT может проиграть простому интерпретатору.
Итог
- Компилятор переводит весь код заранее — быстро, но нужен шаг сборки.
- Интерпретатор выполняет код на лету — гибко, но медленнее.
- Байткод — промежуточные инструкции для виртуальной машины (Python, Java).
- JIT компилирует горячие участки в машинный код во время выполнения.