JIT-компіляціяJust-in-time compilation (JIT) (також відома як dynamic translation або run-time compilation)[1] — компіляція «на льоту» — це технологія збільшення продуктивності програмних систем, що виконують програмний код, шляхом трансляції байт-коду в машинний код безпосередньо під час роботи програми. У такий спосіб досягається висока швидкість виконання за рахунок збільшення споживання пам'яті (для зберігання результатів компіляції) і витрат часу на компіляцію. JIT компіляція є комбінацією двох основних методів трансляції в машинний код, інтерпретації та статичної компіляції, та наслідує якості обох підходів: переваги швидкості скомпільованого коду та гнучкості інтерпретатора поєднані з накладними витратами інтерпретації та компіляції коду. JIT-компіляція є підвидом динамічної компіляції що дозволяє використання технік адаптивної оптимізації, таких як динамічна рекомпіляція, використання інтерпретатором мікроархітектурних оптимізацій.[2] JIT-компіляція підходить для динамічних мов програмування, оскільки системи компіляції реального часу можуть сконструювати пізньо-зв'язуванні типи даних та гарантувати безпеку. Застосування
JIT-компіляція може бути використана у окремих програмах, або для реалізації певного динамічного функціоналу, такого як регулярні вирази. Для прикладу, редактор може скомпілювати регулярний вираз, який був введений під час роботи програми, у швидкий машинний код — цю компіляцію неможливо провести завчасно, оскільки шаблон регулярного виразу вводиться під час виконання. Деякі сучасні середовища виконання покладаються на JIT-компіляцію для підвищення швидкості роботи коду. Прикладами таких середовищ є більшість імплементацій Java та .NET Framework. Схожим чином, багато бібліотек використовують JIT-компіляцію для трансляції регулярних виразів у необхідний байт- або машинний код. JIT компіляція також використовується в деяких емуляторах, з метою трансляції машинного коду процесорів однієї архітектури до машинного коду процесора іншої архітектури. Звичний JIT-компілятор виконує статичну компіляцію перед виконанням, отримуючи байткод (код віртуальної машини), відомий також як байткод компіляція, а після — виконує компіляцію в машинний код (динамічна компіляція, або JIT-компіляція), замість простого процесу інтерпретації байткоду в машинний код. Це дозволяє покращити швидкість виконання коду (порівняно з інтерпретацією), ціною втрати часу на компіляцію. Трансляція коду JIT-компілятором, так само як і інтерпретатором, є безперервним процесом, проте кешування скомпільованого коду зменшує затримку подальшого виконання повторно використаного коду. Оскільки в цьому випадку компілюється тільки частина програми, затримка на компіляцію перед виконанням є меншою, ніж час компіляції всієї програми. Історія розвитку JIT-компіляції, її основних підходівНайпершим опублікованим JIT-компілятором вважається робота Джона Маккарті над LISP у 1960.[3] В його статті «Recursive functions of symbolic expressions and their computation by machine, Part I» (англ. Рекурсивні функції символічних виразів та їхнє обчислення машинами, Частина 1), він згадує функції, що транслюються під час роботи програми, уникаючи необхідності збереження вихідного коду компілятора на перфокартах.[3] (кращим терміном для описаної системи буде «Система компіляції та запуску» (англ. compile and go system). Іншим раннім застосуванням JIT-компіляції є робота Кена Томпсона, шаблонований пошук текстового редактора QED, в якому використовувалась JIT-компіляція регулярних виразів у машинний код IBM 7094, під керівництвом ОС Compatible Time-Sharing System.[3] Великий вплив мав спосіб отримання машинного коду через інтерпретацію, який був використаний у імплементації експериментальної мови програмування LC² компанією Mitchell у 1970 році.[4][5] Мова Smalltalk містила в собі новаторські аспекти JIT-компіляції. Наприклад, трансляція машинного коду виконувалась за потребою, а результат компіляції кешувався для подальшого використання. У випадку нестачі пам'яті, система видаляла видаляла частинки цього коду та відновлювала новою компіляцією за потреби.[6][7] Мова Self, «діалект» мови Smalltalk що був розроблений компанією Sun, розвинула ці техніки та певний час була найшвидшою з сімейства Smalltalk, досягаючи половини швидкості оптимізованого коду на C[8], будучи повністю об'єктно-орієнтованою мовою. Згодом Sun припинили активну розробку Self, однак використали отриманий досвід у мові Java. Термін «Just-in-time компіляція» був запозичений з виробничого терміну «Just in time» та набув популярності у Java — Джеймс Гослінг використовував цей термін з 1993.[9] Зараз JIT-компіляція використовується більшістю імплементацій віртуальної машини Java, оскільки HotSpot бере за основу та активно використовує цю технологію. Проєкт Dynamo[10] компанії HP був експериментальним JIT-компілятором, в якому формат байткоду відповідав машинному коду, система переводила машинний код PA-6000 у машинний код PA-8000. Це призвело до збільшення швидкодії, у деяких випадках до 30 %, оскільки відкрило можливість використовувати оптимізації на рівні машинного коду, наприклад, вбудовування коду для кращого використання кеш-пам'яті, оптимізації викликів динамічних бібліотек та інші, що доступні тільки під час безпосереднього виконання, що робить їх проблематичними для використання звичайними компіляторами.[11][12] 30 березня 2019 року було анонсовано, що PHP 8 отримає JIT-компіляцію[13] у 2021 році.[14] Питання безпекиJIT-компіляція вимагає більшої уваги до питань безпеки та несе підвищені ризики, оскільки має на меті виконання автогенерованого машинного коду. Скомпільований код зберігається в пам'ять та одразу виконується. Цей процес відрізняється від виконання заздалегідь скомпільованого машинного коду тим, що у випадку JIT-компіляції процесор має виконувати код з загальної ділянки пам'яті. Це суперечить ідеї захисту виконавчої ділянки пам'яті, за якої виконання машинного коду має бути дозволене тільки з спеціально відмічених ділянок пам'яті, та навпаки — виконання коду з загальної пам'яті заборонене, оскільки це є слабким місцем захисту від зовнішніх втручань. З цієї причини, сегменти пам'яті з кодом, який був скомпільований на льоту, мають бути відмічені як виконавчі сегменти. З міркувань безпеки, виконавча помітка має бути виставлена після запису коду в пам'ять та виставлення помітки тільки для читання (read-only), оскільки одночасний дозвіл на запис та виконання сегменту пам'яті є потенційною небезпекою (див. W^X).[15] Для прикладу, Javascript JIT-компілятор Firefox'а отримав таку імплементацію у версії Firefox 46.[16] JIT spraying є підвидом експлойта, що використовує JIT-компіляцію як елемент heap spraying атаки, що дозволяє обійти ASLR та захист виконавчого простору, заповнивши купу виконавчим кодом.[17] Див. також
Примітки
Джерела
|