itmo_conspects

Лекция 4. Конвейер компиляции на примере JavaScript

Сейчас большинство веб-приложений написаны на JavaScript - динамически типизируемом языке для скриптов с похожим на язык C синтаксисом

Код на JavaScript могут исполнять множество движков (виртуальных машин)

Хотя в JavaScript есть примитивные типы, многие операции заставляют их вести себя как объекты через автоматическую упаковку в объекты. У объекта в JavaScript есть свойства, у свойства - имя и значения, а сами свойства могут добавляться и удаляться во время исполнения

В JavaScript вместо наследование использованы прототипы: каждый объект хранит в себе ссылку на прототип другого объекта, тем самым наследуя его свойства. Объекты могут представлять собой цепочку прототипов, которую можно изменять во время исполнения

Конвертация типов в JavaScript позволяют выполнять такие операции:

var x = 1 + 2;  // 3
x = 1 + "2";    // "12"
x = 1 + {};     // "1[object Object]"
x = +"23";      // 23

Из-за прототипов, свойств и динамической типизации JavaScript очень динамический, что представляет трудность для реализации виртуальной машины. Из-за этого появляются проблемы в разработке: приходящий тип объекта не соответствует ожидаемому

Далее появился TypeScript, позволяющий аннотировать типы и проверять их перед исполнением программы

Движок JavaScript работает так:

Спецификация JavaScript не указывает формат промежуточного кода, поэтому разработчик движка волен сам выбрать его формат. Интерпретатор также содержит сборщик мусора, который управляет памятью, и совершает оптимизации над кодом

Внутри компилятора:

После этого интерпретатор исполняет байткод

Из-за того, что JavaScript - динамически типизированный, выражение x + y может представлять из себя:

Из-за этого JavaScript обладает низкой производительностью

Главная цель для JavaScript - мгновенно исполнять код, чтобы веб-страницы загружались быстро. Поэтому парсер и интерпретатор не должны тратить ресурсы на оптимизации. Другой путь - компиляция после парсера, что замедляет время запуска

Сейчас в браузерах байткод после парсера сразу исполняется интерпретатором, а в другом потоке горячие регионы - часто вызывающиеся участки кода - компилируются и исполняются нативно

Всего можно выделить 4 уровня:

  1. Уровень 0, интерпретатор - нет оптимизации
  2. Уровень 1, базовые частичные оптимизации без сбора профилей
  3. Уровень 2, базовые частичные оптимизации со сбором профилей
  4. Уровень 3, полностью оптимизированная JIT-компиляция

Если кусок кода вызывается больше какого-то выбранного числа, то далее выбирается нужные оптимизации исходя из информации о выполнении кода

Большая часть веб-приложений и мобильных приложений используют такую гибридную модель: часть кода исполняется в интерпретаторе, часть кода нативно как машинные инструкции

Старая версия движка JavaScriptCore использовала три уровня исполнения

В новой версии добавился новый уровень:

Позднее поняли, что с таким конвейером тяжело работать, поэтому упростили инфраструктуру


Интерпретатор имеет ряд достоинств

И недостатков: большое потребление памяти и низкая скорость по сравнению с машинным кодом

Интерпретацию можно ускорить с помощью AOT- и JIT-компиляций, скрытых классов (структур, с помощью которых можно быстрее обрабатывать сложные объекты). Также можно применить такие оптимизации: