GraalVM. Хайп або панацея

Підписуйтеся на Telegram-канал «DOU #tech», щоб не пропустити нові технічні статті.

Коли я роздумував над тим, що включити в четверту частину мого циклу «Розробка Java додатків», то використання GraalVM було одним з обов’язкових пунктів. У цій статті я хочу розповісти про цей крутой проект і як його можна використовувати.

Проект GraalVM було розпочато 2012 року Томасом Вюртінгером, який працював в швейцарському відділенні Oracle Labs. Саме Oracle є власником і розробником цього проекту, який бере свій початок з іншого проекту — «віртуальна машина Maxine». Після 2018 року Вюртінгер дещо зменшив активність в роботі над проектом, і зараз основну роботу виконують два швейцарця — Дуглас Симон і Крістіан Хамер і австрієць Йозеф Айслі

Який же внутрішній устрій GraalVM? GraalVM включає стандартну HotSpot JDK (з підтримкою компіляції Java додатків). Причому GraalVM Community Edition використовує OpenJDK, а Enterprise Edition — Oracle JDK. Наступний компонент — компілятор, який так і називається Graal. Graal компілятор підтримує вже відому нам JIT компіляцію, при якій JVM байт-код перетворюється в машинний код.

Навіщо потрібен ще один JIT компілятор, якщо в HotSpot JVM вже є два вбудованих — клієнтський C1 і серверний C2? Головна відмінність — GraalVM написаний на Java (а не на C ++, як C1/C2), але, як не дивно, він при цьому більш продуктивний. Більш того, він якраз і розроблявся для високонавантажених додатків. Певний недолік: оскільки Graal написаний на Java, то для його виконання доводиться включати HotSpot JDK в GraalVM.

Але ще більш сильний приріст продуктивності додатка буде при компіляції в AOT режимі. Цей режим давно відомий для розробників фронт-енд додатків, наприклад, на TypeScript. У TypeScript є один мінус — сучасні браузери не вміють виконувати код, написаний на ньому. Тому перед запуском коду його потрібно перетворити (transpile) в JavaScript. Це можна зробити під час виконання (тоді потрібно до додатка підключати TypeScript компілятор), або заздалегідь скомпілювати весь проект в JavaScript bundle. У цьому випадку ми можемо включити додаткову оптимізацію, викинувши невикористаний код (як з нашого проекту, так і з використовуваних бібліотек). Така техніка називається tree shaking, так як чимось схоже на те, як людина трясе дерево, щоб з нього впали гнилі або пошкоджені фрукти.

Що заважало зробити AOT компілятор для Java раніше? Перш за все різні «магічні» фітчи, які підключаються і починають працювати під час завантаження Java додатки. У GraalVM деякі з цих фіч підтримуються за умови їх конфігурації в спеціальному файлі native-image.properties:

• Динамічне завантаження класів
• Reflection API
• JNI
• Динамічні проксі
• Серіалізація
• Ресурсні файли

А ось ці фітчи не підтримуються:

• Security Manager
• Invokedynamic
• CGLib проксi
• Finalizers

Тому розробники Java фреймворків повинні враховувати ці обмеження і конфігурувати свої настройки за допомогою плагінів. До теперішнього часу про підтримку GraalVM оголосили розробники Vert.x, Spring Boot, Helidon, Quarkus і Micronaut.

Для того, щоб зібрати додаток в AOT режимі, потрібно використовувати утиліту Native Image Builder (яка входить в GraalVM), яка включає статичний аналізатор коду. Ця утиліта компілює ваш проект в машинний код, при цьому видаляючи невикористаний код (як з вашого проекту, так і з підключених бібліотек). Одержаний виконуваний файл вже не потребує JVM для свого запуску, але включає спеціальну полегшену віртуальну машину «Substrate VM» для базових інфраструктурних завдань: збірки сміття, керування потоками і деяких інших.

Таким чином, GraalVM порушив один з непорушних постулатів Java-розробки — «Write Once, Run Anywhere». 25 років ми компілювали Java проекти в проміжний байт-код для виконання на будь-який з ОС (за допомогою JVM). А отриманий бiнарний файл можна запустити лише на тій ОС, на якій ми відкомпілювали наш проект за допомогою Native Image Builder. Але все змінила поява віртуалізації і Docker контейнерів. Якщо ми пакуємо додаток в Docker образ (з вже відомою базовою ОС), то нам і не потрібен проміжний етап у вигляді отримання байт-коду. Більш того, так як нам більше не потрібна JDK/JRE в Docker образі, то розмір образу зменшиться в кілька разів, а завантаження програми (вже не вимагає запуску JVM) скоротиться на порядок. Головне, щоб компіляція Native Image Builder займала розумний час.

Тому максимальний виграш у швидкодії/витраті пам’яті ви отримаєте саме при використанні AOT режиму. У цьому він допоможе вирішити одну з найбільших проблем JVM-додатків — низька продуктивність і підвищена витрата пам’яті в порівнянні з тими ж C ++ додатками. Якщо ж ваше додаток його не підтримує, працює з помилками, тоді єдиний варіант — GraalVM і JIT-компілятор.

А як щодо debugging? Утиліта Native Image підтримує спеціальні аргументи для генерації налагоджувальної інформації в GDB-сумісному форматі. На жаль, налагодження Java додатків поки що не підтримується, що в принципі, дуже рідко потрібно для production.

Але і це ще не все. Розробники GraalVM вирішили зробити з неї універсальну віртуальну машину, додавши до підтримки Java спеціальний мовний фреймворк Truffle. Truffle парсит вхідний код проекту і перетворює його в так зване AST дерево інструкцій, яке далі може інтерпретуватися і виконуватися за допомогою GraalVM. На сьогоднішній день підтримуються такі мови як JavaScript, R, Ruby, C, C ++, Python і все JVM-based: Kotlin, Scala, Groovy і Clojure.

Використання Truffle дозволяє робити раніше немислимі речі, наприклад, включати в один клас код, написаний на різних мовах, який потім, після компіляції в Native Image Builder, перетвориться в бiнарний код. Крім того, результати вимірювань показують, що GraalVM випереджає за швидкістю існуючі інтерпретатори того ж Ruby і змагається з інтерпретаторами інших мов.

Загальна схема GraalVM представлена на цьому малюнку.

З відносних мінусів — GraalVM підтримує тільки стабільні LTS версії JDK, наприклад 8-ю або 11-ю. Так як наступна стабільна LTS — JDK 17 вийде тільки восени 2021 року, то на жаль, фітчи з JDK12 + нам поки недоступні.

Ну і так як сама GraalVM (включаючи Truffle) написана на Java, то вона буде працювати не настільки швидко, як би нам цього хотілося, плюс компіляція в native image вимагає і значно більшого часу, ніж звичайна збірка Java і витрати пам’яті (для статичного аналізу коду).

Як ви бачите, у GraalVM є свої переваги і обмеження, але в цілому — це новий захоплюючий етап розробки додатків.

👍НравитсяПонравилось9
В избранноеВ избранном4
LinkedIn
Допустимые теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Допустимые теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Що заважало зробити AOT компілятор для Java раніше?

GCJ был уже в 1998м

З відносних мінусів — GraalVM підтримує тільки стабільні LTS версії JDK, наприклад 8-ю або 11-ю.

Начиная с 11й в jdk есть jaotc.

У цьому він допоможе вирішити одну з найбільших проблем JVM-додатків — низька продуктивність і підвищена витрата пам’яті в порівнянні з тими ж C ++ додатками.

Не поможет. Потребления памяти у java связано с особенностями работы GC, а не JIT
И как уже заметили, в обшем случае AOT код будет медленнее JIT.

Поки що дуже затратно по часу підключити в проект.
Коли можна буде зробити щось типу:

java -jar -graal my.jar

Тоді так, а до цього моменту заморочаться тількі ті кому ну дуже треба.

так. Для чогось більш-менш великого, конфігурація дуже нетривіальна

Ну так вже є плагіни під Spring Boot, Micronaut та інші, які роблять за вас всю чорнову роботу.

Не всі використовують spring, micronaut та ін.

Ну якщо у вас немає ніяких жирних фреймворків з їх «магією», то вам можливо і не потрібна буде додаткова конфігурація для AOT компіляції.

Вона буде, на мінімальному рівні взаємодії з іншими сервісами потрібно буде прописувати рефлекшн конфігурації і т.д

На жаль, потрібна. Ось якраз свіжий комент одного з контрібуторів нетті — twitter.com/...​tatus/1384806232175808513

А в саму нетті вже десь ПРів 20 залетіло, щоб заенейблити Грааль.

Ну так це потрібно буде зробити один раз)

Юзаєм в лямбдах. Для звичайних сервісів оверхед. Хіба що, хайпанути)

Юзаєм в лямбдах.

AWS Lambda? Яким чином і для чого?

cognito triggers та для контенту з s3, який потребує авторизації юзерів на download

схоже на те, як людина трясе дерево, щоб з нього впали гнилі або пошкоджені фрукти.

це коли брагу готують)?

Надnо багато галасу навкруги GraalVM/AOT. Це корисно тільки тоді, коли швидкий старт важливіший за продуктивність додатку, бо АОТ робить неможливими досить суттєві рантайм оптимізації JIT. Тобто для невеликих додатків та виконання в on-demand сервісах на кшталт AWS Lambda. Для типових ентерпрайз додатків (основна цільова аудиторія для Java) воно не дуже актуально.

АОТ робить неможливими досить суттєві рантайм оптимізації JIT.

Чому ви так вирiшили ?

Ну у GraalVM є порівняльний мінус — молодість)
Не забувайте, що JIT компілятор з’явився ще в Java 1.3 і за ці 20 років зазнав чимало поліпшень/оптимізацій.
Ці benchmarks до речі за лютий минулого року, а GraalVM продовжує розвиватися і оптимізувати компіляцію в AOT, так що в майбутньому всі ці розходження повинні зникнути.

Для типових ентерпрайз додатків (основна цільова аудиторія для Java) воно не дуже актуально.

Зараз, коли enterprise якраз масово переходить на мікросервіси, питання старту додатки та витрати пам’яті як ніколи актуальні.

Для типових ентерпрайз додатків (основна цільова аудиторія для Java) воно не дуже актуально

AOT компіляція дозволяє значно зменшити витрати пам’яті і процесорні ресурси, а це ДУЖЕ суттєво для тих сервісів, які працюють в хмарі, тому що дозволяє зберегти фінанси і використовувати менш дорогі instances.

AOT компіляція дозволяє значно зменшити витрати пам’яті і процесорні ресурси

...під час старту. Далі JIT більш ефективний, бо використовує runtime інформацію про hot execution paths та т.п.
В теорії для АОТ з такою метою можна буде використовувати PGO, але, я так розумію, це поки справа майбутнього
github.com/...​79#issuecomment-480786612

Далі JIT більш ефективний

Тільки якщо код дуже херовий і неоптимізований. Інколи JIT більше шкодить чим допомагає. Він теж їсть ЦПУ і РАМ.

Це корисно тільки тоді, коли швидкий старт важливіший за продуктивність додатку,

GraalVM/AOT может убрать зависимость от jvm, но совсем не гарантирует быстрый старт.

В теорії все може бути, але для тих платформ, які вже підтримують native images, час старту в AOT зменшилася на порядок.

Це корисно тільки тоді, коли швидкий старт важливіший за продуктивність додатку

Не тільки. Бінарники жруть на 10-200% менше RAM. А це інколи важливише за втрачені оптимізації JIT.

Подписаться на комментарии