В вычислительной технике JIT - компиляция ( также динамическая трансляция или компиляция во время выполнения ) [1] представляет собой компиляцию ( компьютерного кода ) во время выполнения программы (во время выполнения ), а не перед выполнением. [2] Это может состоять из перевода исходного кода , но чаще всего это перевод байт-кода в машинный код , который затем выполняется напрямую. Система, реализующая JIT-компилятор, обычно непрерывно анализирует исполняемый код и определяет части кода, в которых ускорение, полученное от компиляции или перекомпиляции, перевешивает накладные расходы на компиляцию этого кода.
JIT-компиляция представляет собой комбинацию двух традиционных подходов к трансляции в машинный код — компиляцию с опережением времени (AOT) и интерпретацию — и сочетает в себе некоторые преимущества и недостатки обоих. [2] Грубо говоря, JIT-компиляция сочетает в себе скорость скомпилированного кода с гибкостью интерпретации, с накладными расходами интерпретатора и дополнительными накладными расходами на компиляцию и компоновку (а не только интерпретацию). JIT-компиляция — это форма динамической компиляции , которая обеспечивает адаптивную оптимизацию , такую как динамическая перекомпиляция и ускорение, зависящее от микроархитектуры . [nb 1] [3] Интерпретация и JIT-компиляция особенно подходят для динамических языков программирования , поскольку система времени выполнения может обрабатывать типы данных с поздней привязкой и обеспечивать гарантии безопасности.
Самый ранний опубликованный JIT-компилятор обычно приписывают работе Джона Маккарти над LISP в 1960 году . необходимость сохранять выходные данные компилятора на перфокартах [5] (хотя точнее это было бы называть « системой компиляции и запуска »). Другой ранний пример принадлежит Кену Томпсону , который в 1968 году предложил одно из первых применений регулярных выражений , в данном случае для сопоставления с образцом в текстовом редакторе QED . [6] Для ускорения Томпсон реализовал сопоставление регулярных выражений путем JIT-компиляции с кодом IBM 7094 в совместимой системе разделения времени . [4] Влиятельная техника получения скомпилированного кода из интерпретации была впервые предложена Джеймсом Г. Митчеллом в 1970 году и реализована для экспериментального языка LC² . [7] [8]
Smalltalk (около 1983 г.) стал пионером в новых аспектах JIT-компиляции. Например, трансляция в машинный код выполнялась по требованию, а результат кэшировался для последующего использования. Когда памяти становилось недостаточно, система удаляла часть этого кода и восстанавливала его, когда он снова требовался. [2] [9] Язык Sun Self значительно усовершенствовал эти методы и в какой-то момент был самой быстрой системой Smalltalk в мире, достигая половины скорости оптимизированного C [10] , но с полностью объектно-ориентированным языком.
Компания Sun отказалась от Self, но исследования перешли к языку Java. Термин «компиляция точно в срок» был заимствован из производственного термина « точно в срок » и популяризирован Java, а Джеймс Гослинг использовал этот термин с 1993 года. [11] В настоящее время JIT-компиляция используется в большинстве реализаций виртуальной машины Java. , поскольку HotSpot развивает и широко использует эту исследовательскую базу.
Проект HP Dynamo представлял собой экспериментальный JIT-компилятор, в котором формат «байт-кода» и формат машинного кода были одинаковыми; система оптимизировала машинный код PA-8000 . [12] Как это ни странно, это привело к увеличению скорости, в некоторых случаях на 30%, поскольку это позволило провести оптимизацию на уровне машинного кода, например, встроить код для лучшего использования кэша и оптимизировать вызовы к динамическим библиотекам и многим другим во время выполнения. оптимизации, которые обычные компиляторы не могут выполнить. [13] [14]
В ноябре 2020 года в PHP 8.0 появился JIT-компилятор. [15]
В системе, скомпилированной с помощью байт-кода, исходный код преобразуется в промежуточное представление, известное как байт-код . Байт-код не является машинным кодом для какого-либо конкретного компьютера и может переноситься между компьютерными архитектурами. Затем байт-код может быть интерпретирован или запущен на виртуальной машине . JIT-компилятор считывает байт-коды во многих разделах (или полностью, в редких случаях) и динамически компилирует их в машинный код, чтобы программа могла работать быстрее. Это можно сделать для каждого файла, для каждой функции или даже для любого произвольного фрагмента кода; код можно скомпилировать перед его выполнением (отсюда и название «точно в срок»), а затем кэшировать и повторно использовать позже без необходимости перекомпиляции.
Напротив, традиционная интерпретируемая виртуальная машина будет просто интерпретировать байт-код, как правило, с гораздо меньшей производительностью. Некоторые интерпретаторы даже интерпретируют исходный код без предварительной компиляции в байт-код, что приводит к еще худшей производительности. Статически скомпилированный код или собственный код компилируется перед развертыванием. Среда динамической компиляции — это среда, в которой компилятор может использоваться во время выполнения. Общая цель использования методов JIT — достичь или превзойти производительность статической компиляции , сохраняя при этом преимущества интерпретации байт-кода: большая часть «тяжелой работы» по синтаксическому анализу исходного исходного кода и выполнению базовой оптимизации часто выполняется во время компиляции. перед развертыванием: компиляция из байт-кода в машинный код происходит намного быстрее, чем компиляция из исходного кода. Развернутый байт-код является переносимым, в отличие от собственного кода. Поскольку среда выполнения контролирует компиляцию, как и интерпретируемый байт-код, она может работать в безопасной изолированной программной среде. Компиляторы из байт-кода в машинный код писать легче, поскольку портативный компилятор байт-кода уже выполнил большую часть работы.
JIT-код обычно обеспечивает гораздо лучшую производительность, чем интерпретаторы. Кроме того, в некоторых случаях он может обеспечить более высокую производительность, чем статическая компиляция, поскольку многие оптимизации возможны только во время выполнения: [16] [17]
Поскольку JIT должен отображать и выполнять собственное двоичное изображение во время выполнения, настоящие JIT-компьютеры с машинным кодом требуют платформ, которые позволяют выполнять данные во время выполнения, что делает невозможным использование таких JIT-компиляторов на машине с гарвардской архитектурой ; то же самое можно сказать и о некоторых операционных системах и виртуальных машинах. Однако специальный тип «JIT» потенциально может быть нацелен не на архитектуру ЦП физической машины, а скорее на оптимизированный байт-код виртуальной машины, где преобладают ограничения на необработанный машинный код, особенно там, где виртуальная машина этого байт-кода в конечном итоге использует JIT для собственного кода. [18]
JIT вызывает небольшую или заметную задержку при первоначальном выполнении приложения из-за времени, затрачиваемого на загрузку и компиляцию входного кода. Иногда эту задержку называют «задержкой времени запуска» или «временем прогрева». В общем, чем больше оптимизации выполняет JIT, тем лучше будет сгенерированный код, но и начальная задержка также будет увеличиваться. Поэтому JIT-компилятору приходится искать компромисс между временем компиляции и качеством кода, который он надеется сгенерировать. Время запуска может включать в себя увеличение операций ввода-вывода в дополнение к JIT-компиляции: например, файл данных класса rt.jar для виртуальной машины Java (JVM) составляет 40 МБ, и JVM должна искать много данных в этом контекстно огромном файле. . [19]
Одной из возможных оптимизаций, используемой виртуальной машиной Sun HotSpot Java, является объединение интерпретации и JIT-компиляции. Код приложения изначально интерпретируется, но JVM отслеживает, какие последовательности байт-кода часто выполняются, и преобразует их в машинный код для непосредственного выполнения на оборудовании. Для байт-кода, который выполняется всего несколько раз, это экономит время компиляции и уменьшает начальную задержку; для часто выполняемого байт-кода JIT-компиляция используется для работы на высокой скорости после начальной фазы медленной интерпретации. Кроме того, поскольку программа тратит большую часть времени на выполнение незначительной части своего кода, сокращение времени компиляции является значительным. Наконец, во время первоначальной интерпретации кода можно собрать статистику выполнения перед компиляцией, что помогает лучше оптимизировать. [20]
Правильный компромисс может варьироваться в зависимости от обстоятельств. Например, виртуальная машина Java компании Sun имеет два основных режима — клиентский и серверный. В клиентском режиме выполняется минимальная компиляция и оптимизация для сокращения времени запуска. В режиме сервера выполняется обширная компиляция и оптимизация, чтобы максимизировать производительность после запуска приложения за счет сокращения времени запуска. Другие JIT-компиляторы Java использовали измерение количества выполнений метода во время выполнения в сочетании с размером байт-кода метода в качестве эвристики для принятия решения о том, когда компилировать. [21] Еще один использует количество выполненных операций в сочетании с обнаружением циклов. [22] В целом гораздо сложнее точно предсказать, какие методы оптимизировать в кратковременных приложениях, чем в долговыполняющихся. [23]
Native Image Generator (Ngen) от Microsoft — это еще один подход к уменьшению начальной задержки. [24] Ngen предварительно компилирует (или «предварительно-JIT») байт-код из образа Common Intermediate Language в собственный машинный код. В результате компиляция во время выполнения не требуется. .NET Framework 2.0, поставляемый с Visual Studio 2005, запускает Ngen во всех библиотеках Microsoft DLL сразу после установки. Предварительная синхронизация позволяет сократить время запуска. Однако качество генерируемого им кода может быть не таким хорошим, как код, скомпилированный JIT, по тем же причинам, по которым код, скомпилированный статически, без оптимизации на основе профиля , не может быть таким же хорошим, как код, скомпилированный JIT, в крайнем случае: отсутствие профилирования данных для управления, например, встроенным кэшированием. [25]
Также существуют реализации Java, которые сочетают в себе компилятор AOT (предварительный) либо с JIT-компилятором ( Excelsior JET ), либо с интерпретатором ( компилятор GNU для Java ).
JIT-компиляция может не достичь своей цели, а именно перехода в устойчивое состояние повышенной производительности после короткого начального периода прогрева. [26] [27] На восьми разных виртуальных машинах Барретт и др. (2017) измерили шесть широко используемых микротестов, которые обычно используются разработчиками виртуальных машин в качестве целей оптимизации, многократно запуская их в рамках одного выполнения процесса. [28] В Linux они обнаружили, что от 8,7% до 9,6% выполнений процессов не смогли достичь устойчивого состояния производительности, от 16,7% до 17,9% вошли в устойчивое состояние снижения производительности после периода прогрева и 56,5% пар определенных виртуальная машина, выполняющая определенный тест, не смогла последовательно увидеть устойчивое отсутствие снижения производительности при нескольких выполнениях (т. е. по крайней мере одно выполнение не смогло достичь устойчивого состояния или наблюдалось снижение производительности в устойчивом состоянии). Даже там, где достигалось улучшенное устойчивое состояние, иногда требовались многие сотни итераций. [29] Трайни и др. (2022) вместо этого сосредоточились на виртуальной машине HotSpot, но с гораздо более широким набором тестов, [30] обнаружив, что 10,9% выполнений процессов не смогли достичь устойчивого состояния производительности, а 43,5% тестов не достигли стабильного состояния. через несколько исполнений. [31]
JIT-компиляция в основном использует исполняемые данные и, таким образом, создает проблемы безопасности и возможные эксплойты.
Реализация JIT-компиляции состоит из компиляции исходного кода или байт-кода в машинный код и его выполнения. Обычно это делается непосредственно в памяти: JIT-компилятор выводит машинный код непосредственно в память и немедленно выполняет его, вместо того, чтобы выводить его на диск и затем вызывать код как отдельную программу, как при обычной предварительной компиляции. В современных архитектурах это сталкивается с проблемой из-за защиты исполняемого пространства : произвольная память не может быть выполнена, так как в противном случае существует потенциальная дыра в безопасности. Таким образом, память должна быть помечена как исполняемая; по соображениям безопасности это следует делать после того , как код записан в память и помечен как доступный только для чтения, поскольку записываемая/исполняемая память является дырой в безопасности (см. W^X ). [32] Например, JIT-компилятор Firefox для Javascript представил эту защиту в версии Firefox 46. [33]
JIT-распыление — это класс эксплойтов компьютерной безопасности , которые используют JIT-компиляцию для распыления кучи : полученная память затем становится исполняемой, что позволяет использовать эксплойт, если выполнение может быть перенесено в кучу.
JIT-компиляция может применяться к некоторым программам или может использоваться для определенных возможностей, особенно динамических возможностей, таких как регулярные выражения . Например, текстовый редактор может скомпилировать регулярное выражение, предоставленное во время выполнения, в машинный код, чтобы обеспечить более быстрое сопоставление: этого нельзя сделать заранее, поскольку шаблон предоставляется только во время выполнения. Некоторые современные среды выполнения полагаются на JIT -компиляцию для высокоскоростного выполнения кода, включая большинство реализаций Java вместе с Microsoft .NET . Аналогичным образом, многие библиотеки регулярных выражений поддерживают JIT-компиляцию регулярных выражений либо в байт-код, либо в машинный код. JIT-компиляция также используется в некоторых эмуляторах для перевода машинного кода из одной архитектуры ЦП в другую.
Обычной реализацией JIT-компиляции является сначала компиляция AOT в байт-код ( код виртуальной машины ), известная как компиляция байт-кода , а затем JIT-компиляция в машинный код (динамическая компиляция), а не интерпретация байт-кода. Это повышает производительность во время выполнения по сравнению с интерпретацией за счет задержки из-за компиляции. JIT-компиляторы транслируют непрерывно, как и интерпретаторы, но кэширование скомпилированного кода сводит к минимуму задержку при будущем выполнении того же кода во время данного запуска. Поскольку компилируется только часть программы, задержка значительно меньше, чем если бы перед выполнением компилировалась вся программа.
{{cite journal}}
: Требуется цитировать журнал |journal=
( помощь ){{cite web}}
: CS1 maint: unfit URL (link)