Своевременная компиляция - Just-in-time compilation

В вычислении , точно в срок ( JIT ) компиляции (также динамический перевод или время выполнения компиляции ) представляет собой способ выполнения компьютерного кода , который включает в себя компиляцию во время выполнения программы (на время выполнения ) , а не перед казнью. Это может состоять из трансляции исходного кода, но чаще всего это трансляция байт-кода в машинный код , который затем выполняется напрямую. Система, реализующая JIT-компилятор, обычно непрерывно анализирует выполняемый код и определяет части кода, в которых ускорение, полученное от компиляции или перекомпиляции, перевешивает накладные расходы на компиляцию этого кода.

JIT-компиляция представляет собой комбинацию двух традиционных подходов к трансляции в машинный код - предварительной компиляции (AOT) и интерпретации - и сочетает в себе некоторые преимущества и недостатки обоих. Грубо говоря, JIT-компиляция сочетает в себе скорость компилируемого кода с гибкостью интерпретации, с накладными расходами интерпретатора и дополнительными накладными расходами на компиляцию и компоновку (а не только на интерпретацию). JIT-компиляция - это форма динамической компиляции , которая позволяет адаптивную оптимизацию, такую ​​как динамическая перекомпиляция и ускорение, зависящее от микроархитектуры . Интерпретация и JIT-компиляция особенно подходят для динамических языков программирования , поскольку система времени выполнения может обрабатывать типы данных с поздним связыванием и обеспечивать гарантии безопасности.

История

Самые ранние опубликованные JIT компилятор , как правило , связано с работой на LISP от Джона Маккарти в 1960 г. В его семенные бумажными Рекурсивные функции символических выражений и их вычисление с помощью машины, часть I , он упоминает функции, которые переведены во время выполнения, тем самым избавляя от необходимости в сохраните вывод компилятора на перфокарты (хотя это было бы более точно известно как «система компиляции и запуска »). Другой ранний пример был от Кена Томпсона , который в 1968 году дал одно из первых приложений регулярных выражений здесь для сопоставления с образцом в текстовом редакторе QED . Для ускорения работы Томпсон реализовал сопоставление регулярных выражений с помощью JIT- кода с кодом IBM 7094 в Совместимой системе разделения времени . Влиятельный метод получения скомпилированного кода из интерпретации был впервые предложен Джеймсом Г. Митчеллом в 1970 году, который он реализовал для экспериментального языка LC² .

Smalltalk (ок. 1983 г.) стал пионером в новых аспектах JIT-компиляций. Например, перевод в машинный код выполнялся по запросу, а результат кэшировался для дальнейшего использования. Когда памяти становилось мало, система удаляла часть этого кода и регенерировала его, когда он снова понадобился. Язык Sun's Self значительно улучшил эти методы и в какой-то момент был самой быстрой системой Smalltalk в мире; достижение до половины скорости оптимизированного C, но с полностью объектно-ориентированным языком.

Sun отказалась от Self, но исследования перешли на язык Java. Термин «JIT-компиляция» был заимствован из производственного термина « Just in time » и популяризирован Java, а Джеймс Гослинг использовал этот термин с 1993 года. В настоящее время JITing используется в большинстве реализаций виртуальной машины Java , например HotSpot. опирается на эту исследовательскую базу и широко использует ее.

Проект HP Dynamo был экспериментальным JIT-компилятором, в котором формат «байт-кода» и формат машинного кода были одинаковыми; система превратила машинный код PA-6000 в машинный код PA-8000 . Как ни странно, это привело к ускорению, в некоторых случаях на 30%, поскольку выполнение этого позволило оптимизировать на уровне машинного кода, например, встраивая код для лучшего использования кэша и оптимизируя вызовы динамических библиотек и многие другие оптимизации времени выполнения, которые обычно компиляторы не могут попытаться.

В ноябре 2020 года PHP 8.0 представил JIT-компилятор.

Дизайн

В системе, скомпилированной с байт-кодом, исходный код транслируется в промежуточное представление, известное как байт-код . Байт-код не является машинным кодом для какого-либо конкретного компьютера и может переноситься между компьютерными архитектурами. Затем байт-код может быть интерпретирован или запущен на виртуальной машине . Компилятор JIT считывает байт-коды во многих разделах (или полностью, редко) и динамически компилирует их в машинный код, чтобы программа могла работать быстрее. Это может быть сделано для каждого файла, для каждой функции или даже для любого произвольного фрагмента кода; код может быть скомпилирован перед выполнением (отсюда и название «точно в срок»), а затем кэширован и повторно использован позже без необходимости повторной компиляции.

Напротив, традиционная интерпретируемая виртуальная машина просто интерпретирует байт-код, как правило, с гораздо меньшей производительностью. Некоторые интерпретаторы даже интерпретируют исходный код без предварительной компиляции в байт-код с еще худшей производительностью. Статически скомпилированный код или собственный код компилируется перед развертыванием. Среда динамической компиляции является один , в котором компилятор может быть использован во время выполнения. Общая цель использования методов JIT - достичь или превзойти производительность статической компиляции при сохранении преимуществ интерпретации байт-кода: большая часть «тяжелой работы» по синтаксическому анализу исходного исходного кода и выполнению базовой оптимизации часто выполняется во время компиляции, до развертывания: компиляция из байт-кода в машинный код выполняется намного быстрее, чем компиляция из исходного кода. Развернутый байт-код переносим, ​​в отличие от нативного кода. Поскольку среда выполнения контролирует компиляцию, как и интерпретируемый байт-код, она может выполняться в безопасной песочнице. Компиляторы из байт-кода в машинный код писать легче, потому что переносимый компилятор байт-кода уже проделал большую часть работы.

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

  1. Компиляцию можно оптимизировать для целевого процессора и модели операционной системы, в которой выполняется приложение. Например, JIT может выбрать векторные инструкции ЦП SSE2, когда обнаружит, что ЦП их поддерживает. Чтобы получить такой уровень специфичности оптимизации с помощью статического компилятора, нужно либо скомпилировать двоичный файл для каждой предполагаемой платформы / архитектуры, либо включить несколько версий частей кода в один двоичный файл.
  2. Система может собирать статистику о том, как программа фактически работает в среде, в которой она находится, а также может перекомпилировать и перекомпилировать для достижения оптимальной производительности. Однако некоторые статические компиляторы также могут принимать в качестве входных данных информацию профиля.
  3. Система может выполнять глобальную оптимизацию кода (например, встраивание библиотечных функций) без потери преимуществ динамической компоновки и без накладных расходов, присущих статическим компиляторам и компоновщикам. В частности, при выполнении глобальных встроенных подстановок процессу статической компиляции могут потребоваться проверки во время выполнения и гарантировать, что виртуальный вызов произойдет, если фактический класс объекта переопределяет встроенный метод, и может потребоваться обработка проверок граничных условий при доступе к массиву. внутри петель. При своевременной компиляции во многих случаях эту обработку можно вывести из цикла, что часто приводит к значительному увеличению скорости.
  4. Хотя это возможно со статически скомпилированными языками со сборкой мусора, система байт-кода может более легко переупорядочить исполняемый код для лучшего использования кеша.

Поскольку JIT должен визуализировать и выполнять собственный двоичный образ во время выполнения, для истинных JIT с машинным кодом требуются платформы, которые позволяют выполнять данные во время выполнения, что делает использование таких JIT на машине с архитектурой Гарварда невозможным; то же самое можно сказать и о некоторых операционных системах и виртуальных машинах. Однако специальный тип «JIT» потенциально может не нацеливаться на архитектуру ЦП физического компьютера, а скорее на оптимизированный байт-код виртуальной машины, где преобладают ограничения на исходный машинный код, особенно когда виртуальная машина этого байт-кода в конечном итоге использует JIT для собственного кода.

Представление

JIT вызывает небольшую или заметную задержку в начальном выполнении приложения из-за времени, затрачиваемого на загрузку и компиляцию байт-кода. Иногда эту задержку называют «время запуска» или «время прогрева». В общем, чем больше выполняется JIT-оптимизация, тем лучше будет сгенерированный код, но начальная задержка также увеличится. Поэтому JIT-компилятор должен найти компромисс между временем компиляции и качеством кода, который он надеется сгенерировать. Время запуска может включать увеличение количества операций, связанных с вводом-выводом, в дополнение к JIT-компиляции: например, файл данных класса rt.jar для виртуальной машины Java (JVM) составляет 40 МБ, и JVM должна искать много данных в этом контекстно огромном файле. .

Одна из возможных оптимизаций, используемых виртуальной машиной Sun HotSpot Java, - это объединение интерпретации и JIT-компиляции. Код приложения изначально интерпретируется, но JVM отслеживает, какие последовательности байт-кода часто выполняются, и переводит их в машинный код для прямого выполнения на оборудовании. Для байт-кода, который выполняется всего несколько раз, это экономит время компиляции и снижает начальную задержку; для часто выполняемого байт-кода JIT-компиляция используется для работы на высокой скорости после начальной фазы медленной интерпретации. Кроме того, поскольку программа тратит большую часть времени на выполнение меньшей части своего кода, сокращение времени компиляции является значительным. Наконец, во время первоначальной интерпретации кода статистика выполнения может быть собрана перед компиляцией, что помогает выполнить лучшую оптимизацию.

Правильный компромисс может варьироваться в зависимости от обстоятельств. Например, виртуальная машина Java Sun имеет два основных режима - клиентский и серверный. В клиентском режиме выполняется минимальная компиляция и оптимизация, чтобы сократить время запуска. В серверном режиме выполняется обширная компиляция и оптимизация, чтобы максимизировать производительность после запуска приложения, жертвуя временем запуска. Другие JIT-компиляторы Java использовали измерение времени выполнения, определяющее количество выполнений метода в сочетании с размером байт-кода метода, в качестве эвристики, чтобы решить, когда выполнять компиляцию. Еще один использует количество выполненных операций в сочетании с обнаружением циклов. В общем, гораздо сложнее точно предсказать, какие методы следует оптимизировать в краткосрочных приложениях, чем в долго работающих.

Native Image Generator (Ngen) от Microsoft - еще один подход к уменьшению начальной задержки. Ngen предварительно компилирует (или "pre-JIT") байт-код из образа Common Intermediate Language в машинный код. В результате компиляция среды выполнения не требуется. .NET Framework 2.0, поставляемый с Visual Studio 2005, запускает Ngen во всех библиотеках DLL Microsoft сразу после установки. Предварительное срабатывание позволяет сократить время запуска. Однако качество генерируемого кода может быть не таким хорошим, как у JIT-компилятора, по тем же причинам, по которым код, скомпилированный статически, без оптимизации на основе профиля , не может быть таким же хорошим, как JIT-скомпилированный код в крайнем случае: отсутствие профилирования данных для управления, например, встроенным кэшированием.

Также существуют реализации Java, в которых компилятор AOT (опережающий время) сочетается либо с JIT-компилятором ( Excelsior JET ), либо с интерпретатором ( компилятор GNU для Java ).

Безопасность

JIT-компиляция в основном использует исполняемые данные и, таким образом, создает проблемы безопасности и возможные эксплойты.

Реализация JIT-компиляции состоит из компиляции исходного кода или байтового кода в машинный код и его выполнения. Обычно это выполняется непосредственно в памяти: JIT-компилятор выводит машинный код непосредственно в память и немедленно выполняет его, а не выводит на диск, а затем вызывает код как отдельную программу, как при обычной предварительной компиляции. В современных архитектурах это вызывает проблему из-за защиты исполняемого пространства : произвольная память не может быть выполнена, поскольку в противном случае существует потенциальная дыра в безопасности. Таким образом, память должна быть помечена как исполняемая; по соображениям безопасности это должно быть сделано после того, как код был записан в память и помечен как доступный только для чтения, поскольку доступная для записи / исполняемая память представляет собой дыру в безопасности (см. W ^ X ). Например, JIT-компилятор Firefox для Javascript представил эту защиту в выпускной версии Firefox 46.

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

Использует

JIT-компиляция может применяться к некоторым программам или может использоваться для определенных возможностей, особенно динамических возможностей, таких как регулярные выражения . Например, текстовый редактор может скомпилировать регулярное выражение, предоставленное во время выполнения, в машинный код, чтобы обеспечить более быстрое сопоставление: это невозможно сделать раньше времени, поскольку шаблон предоставляется только во время выполнения. Некоторые современные среды выполнения полагаться на JIT компиляции для выполнения высокоскоростных кодов, в том числе большинство реализаций Java вместе с Microsoft «s .NET Framework . Точно так же многие библиотеки регулярных выражений поддерживают JIT-компиляцию регулярных выражений либо в байт-код, либо в машинный код. JIT-компиляция также используется в некоторых эмуляторах для перевода машинного кода из одной архитектуры ЦП в другую.

Распространенной реализацией JIT-компиляции является сначала AOT-компиляция в байт-код (код виртуальной машины ), известная как компиляция байт-кода , а затем JIT-компиляция в машинный код (динамическая компиляция), а не интерпретация байт-кода. Это улучшает производительность во время выполнения по сравнению с интерпретацией за счет задержки из-за компиляции. Компиляторы JIT выполняют перевод непрерывно, как и интерпретаторы, но кэширование скомпилированного кода минимизирует задержку при выполнении того же кода в будущем во время данного прогона. Поскольку компилируется только часть программы, задержка значительно меньше, чем если бы вся программа была скомпилирована перед выполнением.

Смотрите также

Примечания

использованная литература

дальнейшее чтение

внешние ссылки