Interested Article - JIT-компиляция
- 2021-06-28
- 1
JIT-компиляция ( англ. Just-in-Time , компиляция «точно в нужное время»), динамическая компиляция ( англ. dynamic translation ) — технология увеличения производительности программных систем, использующих байт-код , путём компиляции байт-кода в машинный код или в другой формат непосредственно во время работы программы. Таким образом достигается высокая скорость выполнения по сравнению с интерпретируемым байт-кодом (сравнимая с компилируемыми языками) за счёт увеличения потребления памяти (для хранения результатов компиляции) и затрат времени на компиляцию. Технология JIT базируется на двух более ранних идеях, касающихся среды выполнения: компиляции байт-кода и динамической компиляции .
Так как JIT-компиляция является, по сути, одной из форм динамической компиляции, она позволяет применять такие технологии, как адаптивная оптимизация и . Благодаря этому JIT-компиляция может показывать лучшие результаты в плане производительности, чем статическая компиляция. Интерпретация и JIT-компиляция особенно хорошо подходят для динамических языков программирования , при этом среда исполнения справляется с поздним связыванием типов и гарантирует безопасность исполнения.
Проекты LLVM , GNU Lightning , libJIT (часть проекта DotGNU ) и RPython (часть проекта PyPy ) могут быть использованы для создания JIT-интерпретаторов любого скриптового языка.
Особенности реализации
JIT-компиляция может быть применена как ко всей программе, так и к её отдельным частям. Например, текстовый редактор может на лету компилировать регулярные выражения для более быстрого поиска по тексту. С AOT-компиляцией такое сделать не представляется возможным для случаев, когда данные предоставляются во время исполнения программы, а не в момент компиляции. JIT используется в реализациях Java (JRE), JavaScript , .NET Framework , в одной из реализаций Python — PyPy . Существующие наиболее распространённые интерпретаторы языков PHP , Ruby , Perl , Python и им подобных имеют ограниченные или неполные JIT.
Большинство реализаций JIT имеет последовательную структуру: сначала приложение компилируется в байт-код виртуальной машины среды исполнения (AOT-компиляция), а потом JIT компилирует байт-код непосредственно в машинный код. В итоге при запуске приложения тратится лишнее время, что впоследствии компенсируется более быстрой его работой.
Описание
В языках, таких как Java , PHP , C# , Lua , Perl , GNU CLISP , исходный код транслируется в одно из промежуточных представлений , называемое байт-кодом . Байт-код не является машинным кодом какого-либо конкретного процессора и может переноситься на различные компьютерные архитектуры и исполняться точно так же. Байт-код интерпретируется (исполняется) виртуальной машиной . JIT читает байт-код из некоторых секторов (редко сразу из всех) и компилирует их в машинный код. Этим сектором может быть файл, функция или любой фрагмент кода. Единожды скомпилированный код может кэшироваться и в дальнейшем повторно использоваться без перекомпиляции.
Динамически компилируемая среда — это среда, в которой компилятор может вызываться приложением во время выполнения. Например, большинство реализаций
Common Lisp
содержат функцию
compile
, которая может создать функцию во время выполнения; в Python это функция
eval
. Это удобно для программиста, так как он может контролировать, какие части кода действительно подлежат компиляции. Также с помощью этого приёма можно компилировать динамически сгенерированный код, что в некоторых случаях приводит даже к лучшей производительности, чем реализация в статически скомпилированном коде. Однако стоит помнить, что подобные функции могут быть опасны, особенно когда данные передаются из недоверенных источников.
Основная цель использования JIT — достичь и превзойти производительность статической компиляции, сохраняя при этом преимущества динамической компиляции:
- Большинство тяжеловесных операций, таких, как парсинг исходного кода и выполнение базовых оптимизаций, происходит во время компиляции (до развёртывания), в то время как компиляция в машинный код из байт-кода происходит быстрее, чем из исходного кода.
- Байт-код более переносим (в отличие от машинного кода).
- Среда может контролировать выполнение байт-кода после компиляции, поэтому приложение может быть запущено в песочнице (для нативных программ такая возможность тоже существует, но реализация данной технологии сложнее).
- Компиляторы из байт-кода в машинный код легче в реализации, так как большинство работы по оптимизации уже было проделано компилятором.
JIT, как правило, эффективней, чем интерпретация кода. К тому же в некоторых случаях JIT может показывать большую производительность по сравнению со статической компиляцией за счёт оптимизаций, возможных только во время исполнения:
- Компиляция может осуществляться непосредственно для целевого процессора и операционной системы, на которой запущено приложение. Например, JIT может использовать векторные SSE2 расширения процессора, если он обнаружит их поддержку.
- Среда может собирать статистику о работающей программе и производить оптимизации с учётом этой информации. Некоторые статические компиляторы также могут принимать на вход информацию о предыдущих запусках приложения.
- Среда может делать глобальные оптимизации кода (например, встраивание библиотечных функций в код) без потери преимуществ динамической компиляции и без , присущих статическим компиляторам и компоновщикам .
- Более простое перестраивание кода для лучшего использования кэша .
Задержка при запуске, средства борьбы с ней
Типичная причина задержки при запуске JIT-компилятора — расходы на загрузку среды и компиляцию приложения в машинный код. В общем случае, чем лучше и чем больше оптимизаций выполняет JIT, тем больше получается задержка. Поэтому разработчикам JIT приходится искать компромисс между качеством генерируемого кода и временем запуска. Однако, часто оказывается так, что узким местом в процессе компиляции оказывается не сам процесс компиляции, а задержки системы ввода-вывода (так, например, rt.jar в Java Virtual Machine (JVM) имеет размер 40 МБ, и поиск метаданных в нём занимает достаточно большое количество времени).
Ещё одно средство оптимизации — компилировать только те участки приложения, которые используются чаще всего. Этот подход реализован в PyPy и HotSpot Java Virtual Machine компании Sun Microsystems .
В качестве эвристики может использоваться счётчик запусков участков приложения, размер байт-кода или детектор циклов.
Порой достаточно сложно найти правильный компромисс. Так, например, Sun’s Java Virtual Machine имеет два режима работы — клиент и сервер. В режиме клиента количество компиляций и оптимизаций минимально для более быстрого запуска, в то время как в режиме сервера достигается максимальная производительность, но из-за этого увеличивается время запуска.
Ещё одна техника, называемая pre-JIT, компилирует код до запуска. Преимуществом данной техники является уменьшенное время запуска, в то же время недостатком является плохое качество скомпилированного кода по сравнению с runtime JIT.
История
Самую первую реализацию JIT можно отнести к LISP, написанную McCarthy в 1960 году . В его книге Recursive functions of symbolic expressions and their computation by machine, Part I он упоминает функции, компилируемые во время выполнения, тем самым избавив от надобности вывода работы компилятора на перфокарты .
Другой ранний пример упоминания JIT можно отнести к Кену Томпсону , который в 1968 году впервые применил регулярные выражения для поиска подстрок в текстовом редакторе QED . Для ускорения алгоритма Томпсон реализовал компиляцию регулярных выражений в машинный код IBM 7094 .
Метод получения скомпилированного кода был предложен Митчелом в 1970 году , когда он реализовал экспериментальный язык LC 2 .
Smalltalk (1983) был пионером в области JIT-технологий. Трансляция в машинный код выполнялась по требованию и кэшировалась для дальнейшего использования. Когда память кончалась, система могла удалить некоторую часть кэшированного кода из оперативной памяти и восстановить его, когда он снова потребуется. Язык программирования Self некоторое время был самой быстрой реализацией Smalltalk и работал всего лишь в два раза медленней C , будучи полностью объектно-ориентированным.
Self был заброшен Sun, но исследования продолжились в рамках языка Java. Термин «Just-in-time компиляция» был заимствован из производственного термина «Точно в срок» и популяризован Джеймсом Гослингом , использовавшим этот термин в 1993. В данный момент JIT используется почти во всех реализациях Java Virtual Machine.
Также большой интерес представляет диссертация, защищённая в 1994 году в Университете ETH (Швейцария, Цюрих) Михаэлем Францем «Динамическая кодогенерация — ключ к переносимому программному обеспечению» и реализованная им система Juice динамической кодогенерации из переносимого семантического дерева для языка Оберон . Система Juice предлагалась как плагин для интернет-браузеров.
Безопасность
Так как JIT составляет исполняемый код из данных, возникает вопрос безопасности и возможных уязвимостей.
JIT компиляция включает в себя компиляцию исходного кода или байт-кода в машинный код и его выполнение. Как правило, результат записывается в память и исполняется сразу же, без промежуточного сохранения на диск или его вызов как отдельной программы. В современных архитектурах для повышения безопасности произвольные участки памяти как машинный код ( NX bit ). Для корректного запуска регионы памяти должны быть предварительно помечены как исполняемые, при этом для большей безопасности флаг исполнения может ставиться только после снятия флага разрешения записи (Схема защиты W^X) .
См. также
- AOT
- Двоичная трансляция
- HotSpot
- Common Language Runtime
- Crusoe
- GNU Lightning
- LLVM
- Самомодифицирующийся код
Примечания
- от 27 августа 2017 на Wayback Machine , 2008, ISBN 9788177228366 , Dreamtech Press, 2008. p.12
- . Дата обращения: 27 августа 2017. 19 сентября 2017 года.
- Benjamin Peterson — от 12 мая 2008 на Wayback Machine
- от 13 сентября 2014 на Wayback Machine , habrahabr
- Aycock 2003, 2. JIT Compilation Techniques, 2.1 Genesis, p. 98.
- Aycock 2003, 2. JIT Compilation Techniques, 2.2 LC², p. 98-99.
- Mitchell, J.G. (1970). The design and construction of flexible and efficient interactive programming systems .
- Aycock & 2003 2.14 Java, p. 107, footnote 13.
- от 26 сентября 2017 на Wayback Machine ; от 7 сентября 2017 на Wayback Machine
- . Дата обращения: 7 ноября 2009. 23 декабря 2009 года.
- от 2 августа 2017 на Wayback Machine / Phoronix, 4 January 2016 (англ.)
- 2021-06-28
- 1