Компиляторы

Интерпретатор vs Компилятор

2008 год. Google выпускает Chrome с JavaScript движком V8. До этого JavaScript работал в 100 раз медленнее Java. V8 вводит JIT-компиляцию: первые выполнения интерпретируются, горячий код компилируется в машинный в рантайме. Node.js (2009) построен на V8 - и внезапно серверный JavaScript становится конкурентом Java по производительности. Один архитектурный выбор (JIT) изменил весь веб-стек за несколько лет.

  • **Python**: CPython - классический bytecode интерпретатор. PyPy добавляет JIT - ускорение в 5-50 раз для числодробилок
  • **JVM**: HotSpot JIT компилирует горячие методы в нативный код - Java конкурирует с C++ по скорости
  • **V8**: Ignition (bytecode интерпретатор) + TurboFan (оптимизирующий JIT компилятор)
  • **LLVM**: AOT компилятор backend для C, Rust, Swift, Julia - один IR, много архитектур

Интерпретаторы и компиляторы: 70 лет эволюции

В 1957 году Fortran compiler Джона Бэкуса произвёл революцию: генерировал код настолько хорошо, что скептики не верили - 'вручную так не написать'. Это был AOT компилятор. В 1958 году LISP Джона Маккарти ввёл интерпретацию: код выполнялся напрямую через eval. В 1995 году Java выбрала средний путь: компиляция в bytecode + интерпретация JVM. HotSpot (1999) добавил JIT. В 2008 году V8 показал, что JIT может сделать динамический язык быстрым. Современные языки (Julia, PyPy) используют все три подхода одновременно.

Tree-Walking Interpreter

Простейший интерпретатор: после парсинга AST обходит дерево и выполняет каждый узел напрямую. Нет промежуточной компиляции.

**Преимущества tree-walking:** Простота реализации, легкость добавления новых конструкций, удобство для DSL и скриптовых языков. **Недостатки:** Медленно - каждый switch/dispatch на каждый узел. Плохой cache locality из-за pointer-chasing по куче. Ruby 1.8 и ранние Python использовали этот подход.

Tree-walking интерпретатор выполняет цикл `for i in range(1000000): x += i`. Что является главной причиной медленной работы?

Bytecode VM

Bytecode VM компилирует AST в компактный промежуточный код (bytecode), затем исполняет его виртуальной машиной. Быстрее tree-walking, но переносимо как интерпретатор.

CPython компилирует Python в bytecode, который исполняет CPython VM. Означает ли это, что Python - компилируемый язык?

JIT: Just-In-Time компиляция

JIT компилятор анализирует выполнение программы в рантайме и компилирует горячие участки кода в нативный машинный код. Объединяет гибкость интерпретатора со скоростью компилятора.

**Hidden Classes (V8):** V8 создаёт внутренние классы для объектов с одинаковой структурой. `{x: 1, y: 2}` все экземпляры получают один Hidden Class -> JIT может инлайнить property access. Добавление свойств в разном порядке создаёт разные Hidden Classes -> дополниться с polymorphic lookup.

V8 JIT оптимизировал функцию `sum(a, b)` для integer входных данных. Вызов `sum(3.14, 2.71)` вызывает deoptimization. Что происходит дальше?

AOT: Ahead-Of-Time компиляция

AOT компилятор генерирует нативный машинный код до запуска программы. Максимальная производительность, предсказуемое время запуска. C, C++, Rust, Go, Swift используют AOT.

ПодходStartupPeak PerfPortabilityПримеры
Tree-walkingМгновенныйНизкаяВысокаяRuby 1.8, Bash
Bytecode VMБыстрыйСредняяВысокаяPython, Java (без JIT)
JITПрогрев ~секОчень высокаяСредняяV8, JVM HotSpot, PyPy
AOTМгновенныйМаксимальнаяНизкая (re-compile)C, Rust, Go, Swift

**LLVM как универсальный backend:** LLVM IR - промежуточное представление для AOT компиляторов. C (Clang), Rust, Swift, Julia, Kotlin Native - все используют LLVM backend. Один IR -> оптимизации -> x86, ARM, RISC-V, WebAssembly. Это объясняет, почему Rust получает C-уровень производительности без написания собственного кодогенератора.

Компилируемые языки всегда быстрее интерпретируемых

Современные JIT компиляторы (V8, JVM HotSpot) часто достигают производительности AOT компиляторов для долгоживущих программ.

JIT имеет информацию о рантайм профиле: какие ветки чаще выполняются, какие типы реально используются. AOT оптимизирует без этой информации. JVM с JIT часто быстрее эквивалентного C кода для числодробилок.

AWS Lambda функция на Python запускается под нагрузкой тысячи раз в секунду. Почему cold start (первый запуск) медленнее warm start?

Ключевые идеи

  • **Tree-walking interpreter**: прямое выполнение AST - просто, медленно. CPython 2.x, Ruby 1.8
  • **Bytecode VM**: компиляция в промежуточный код + интерпретация. Python 3, Java, Lua
  • **JIT (Just-In-Time)**: bytecode -> нативный код в рантайме для горячих участков. V8, JVM HotSpot
  • **AOT (Ahead-of-Time)**: компиляция до запуска -> максимальная скорость. C, Rust, Go
  • **Trade-off**: startup time vs peak performance vs portability vs dynamism

Вопросы для размышления

  • JIT требует времени для 'прогрева' - горячий код компилируется после нескольких интерпретированных выполнений. Для каких приложений это проблема (serverless функции vs long-running server)?
  • PyPy - Python интерпретатор написанный на Python с JIT. Как это возможно? Что такое meta-tracing JIT?
  • Wasm (WebAssembly) - это IR формат или bytecode? Как браузеры выполняют Wasm: интерпретацией, AOT или JIT?

Связанные уроки

  • plt-02-type-systems
  • arch-04-cpu
Интерпретатор vs Компилятор

0

1

Войти