На главной конференции Сибирской Явы JBreak-2018 , проведённом в Новосибирске Кристианом Талингером из Твиттер поделился практическим опытом использования Graal. Компания (Петер-Сервис) отправила на конференцию всю нашу рабочую группу, и мы пришли послушать этот доклад полностью.
Это и понятно, учитывая тот факт, что Graal до сих пор считается смелым и потенциально опасным экспериментом (хотя весьма вероятно, что он будет включен в JDK 10).
Было очень интересно узнать, как эта новинка проявляет себя в бою - и не где-нибудь, а в разработке такого уровня.
Кристиан Талингер работает с виртуальными машинами Java более десятка лет, и ключевой навык в его компетенции — JIT-компиляторы.
Именно Кристиан внедрил Graal и инициировал его нынешнее (очень активное, по словам Криса) использование в производственной среде Twitter. И, по словам Талингера, это нововведение экономит компании много денег за счет экономии ресурсов железа.
Здесь, в этом интервью С организаторами JBreak Кристиан доходчиво объясняет основы — что такое Graal и как им управлять.
Ну а доклад в Новосибирске был скорее практикоориентированным: его главная задача была показать аудитории, насколько легко и безболезненно начать работать с Graal и почему стоит попробовать.
Для начала еще пара теоретических вступительных замечаний.
Итак, что же такое JIT — JIT-компилятор? Запуск Java-программы требует нескольких шагов: сначала скомпилировать исходный код в инструкции для JVM — байт-код, а затем запустить этот байт-код в JVM. Здесь JVM выступает в роли интерпретатора.
JIT-компилятор был создан для ускорения работы Java-приложений: он оптимизирует работающий байт-код, переводя его в низкоуровневые машинные инструкции непосредственно во время выполнения программы.
HotSpot/OpenJDK использует два уровня JIT-компиляции, реализованные на C++.
Это C1 и C2 (также известные как клиент и сервер).
По умолчанию они работают вместе: сначала выполняется быстрая, но поверхностная оптимизация с помощью C1, а затем самые горячие методы дополнительно оптимизируются с помощью C2. В Java 9 внутри ДЖЭП-243 реализован механизм встраивания в JVM компилятора, написанного на Java. И это динамический компилятор — JVMCI (Java Virtual Machine Compiler Interface).
Собственно, это механизм, поддерживающий Graal. Надо сказать, что в Java 9 Graal уже был доступен в составе ДЖЭП-295 - AOT-компиляция (заранее времени) в JVM. Правда, хотя механизмы компиляции AOT используют Graal в качестве компилятора, в этом JEP указано, что изначально интеграция кода Graal в JDK предназначена только в рамках платформы Linux/x64. Итак, чтобы попробовать Graal, вам нужно получить JDK с AOT и JMVCI. Более того, если вам необходимо работать на платформах MacOS или Windows, вам придется дождаться выхода Java 10 (в соответствующем билете).
JDK-8172670 fix-версия входит в десятку лучших).
Здесь Кристиан обратил внимание слушателей на то, что в текущих дистрибутивах JDK версия Graal, мягко говоря, устарела (то ли годичной давности, то ли еще моложе).
Но здесь нам на помощь приходит модульность Java 9. Благодаря этому мы можем собрать последнюю версию Graal из исходников и интегрировать ее в JVM с помощью команды --upgrade-module-path. Поскольку разработка Graal началась задолго до модульной системы, для ее сборки используется специальный инструмент — mx, который в некоторой степени копирует модульную систему Java. Инструмент работает на Python 2.7, все ссылки можно найти в Репозитории Graal на GitHub .
То есть сначала скачиваем и устанавливаем mx, затем скачиваем Graal и собираем его в модуль через mx, который потом заменит исходный модуль в JDK. На первый взгляд эти манипуляции могут показаться сложными и трудоемкими, но на самом деле этот черт не так уж и страшен.
И в принципе возможность заменить версию Graal, не дожидаясь выхода патча к JDK или даже нового JDK, мне кажется более чем удобной.
По крайней мере, Кристиан показал, как он все это собрал вживую на машинах в облаке.
Однако при сборке Truffle произошла ошибка — потребовались некоторые дополнительные зависимости, установленные на машине.
Но Грааль был собран правильно и далее использовался именно в таком виде (из чего делаем вывод, что о Трюфеле можно вообще забыть: Грааль от него совершенно независим).
Далее: чтобы JVM начала использовать Graal, нужно дополнительно установить 3 флага:
Поскольку Graal по сути является обычным Java-приложением, его также необходимо скомпилировать и подготовить к работе (так называемая начальная загрузка).-XX:+UnlockExperimentalVMOptions -XX:+UseJVMCICompiler -XX:-EnableJVMCI
В режиме «по умолчанию» (по требованию) это происходит параллельно с запуском приложения, и в этом случае Graal использует C1 для оптимизации своего кода.
Также есть возможность явно запустить инициализацию перед запуском приложения, и в этом случае вы можете даже поручить Graal оптимизировать себя.
Однако обычно это занимает гораздо больше времени и не дает существенных преимуществ.
Грааль инициализируется немного дольше, чем C1/C2, и активнее использует свободную мощность процессора из-за того, что ему необходимо скомпилировать больше классов.
Но эти различия не столь велики и практически нивелируются, теряясь в общем шуме при инициализации приложения.
Кроме того, поскольку Graal написан на Java, для инициализации он использует кучу (в случае C1/C2 также используется память, только через malloc).
Основное потребление памяти происходит при старте приложения.
И Graal, и C1/C2 при компиляции используют свободные ядра.
Потребление памяти Graal можно отслеживать, включив ведение журнала GC (на данный момент не предусмотрена изоляция кучи для инициализации Graal от основной кучи приложения).
Что ж, теперь, когда мы узнали, как все это настроить, пришло время понять, почему.
Какие преимущества нам даст использование Graal? Кристиан использовал практический пример, чтобы ответить на этот вопрос.
Он запустил пару тестов из одного и того же проекта, написанного на Scala: один активно работал с процессором, а другой более активно взаимодействовал с памятью.
На бенчмарке, работающем на CPU, при использовании Graal наблюдалось заметное замедление в среднем на секунду из-за более длительного старта (сам бенчмарк работал 5 секунд).
А вот на втором бенчмарке Graal показал вполне хороший результат — ~20 секунд против ~28 на C1/C2. И это при том, что, как заметил Кристиан, в примере со Scala Graal работает не так хорошо, как мог бы (из-за динамической структуры байт-кода, генерируемого Scala).
То есть можно надеяться, что в случае с чистым Java-приложением все должно быть еще лучше.
Плюс при выводе логов GC было видно, что с Graal приложение выполняет гораздо меньше сборок мусора (примерно в 2 раза).
Это связано с более эффективным escape-анализом, который позволяет оптимизировать количество объектов, создаваемых в куче.
Подводя итог своим личным впечатлениям от услышанного, скажу, что доклад показался мне вполне исчерпывающим, и вовсе не несущим рекламного послания в духе «всем срочно переходить на Грааль».
Понятно, что волшебной таблетки не существует, и все всегда определяется реальным применением — сам Кристиан признает, что конкретные значения, конечно, зависят от конкретных ориентиров.
Тем, кто решит попробовать Грааль, в любом случае придется методом научного тыка, запускать и (наверняка) находить баги (а еще лучше редактировать их и выдавать пул-реквесты в репозиторий Грааля).
Но в целом при нынешней тенденции к использованию микросервисов и приложений без сохранения состояния — и, как следствие, к более активному (и правильному) использованию Young Gen — Graal выглядит очень неплохо.
Так что, если проект можно легко перевести на Java 9 (или написать на нем с нуля), я бы обязательно попробовал Graal. А меня, например, даже порадовало, что весь акцент в докладе был сделан именно на Graal как JIT-компиляторе — потому что в целом среднестатистическому Java-разработчику он нужен именно в этом качестве (то есть без Трюфеля и прочего GraalVM , которую Oracle недавно объединила в среду разработки и среду выполнения для различных языков на основе JVM).
Было бы интересно также протестировать затраты памяти и посмотреть, насколько заметна разница между стандартным C1/C2 и Graal. С другой стороны, несмотря на то, что в наше время приложению выделяется довольно приличный объем памяти, и основная ее часть потребляется при запуске (а сегодня это обычно инициализация и запуск контейнера, который уже запускает само приложение), эти цифры, судя по всему, в любом случае не столь значительны.
Здесь Вы можете скачать презентацию из отчета.
Честно говоря, меня лично настолько заинтересовала эта идея, что я планирую повторить все шаги, проделанные Кристианом, но попробовать запустить наборы тестов Java напрямую (например, DaCapo и SPECjvm2008 — я не так хорош в тестировании Java).
, поэтому буду признателен, если кто-то предложит в комментариях или личке более адекватные варианты).
Ну и ближе к конкретике работы — попробую набросать простое веб-приложение (например, SpringBoot+Jetty+PostgreSQL), запустить его под нагрузкой и сравнить цифры.
Обещаю поделиться результатами с сообществом.
Теги: #программирование #java #jit-компилятор #graal #java 9
-
Понимание Node.js
19 Oct, 24