Разговоры о возможном появлении в HotSpot статической компиляции шли давно. Но с недавних пор, когда соответствующий JEP 295 получил статус «targeted», появилась определённость: она попадёт в HotSpot с выходом Java 9 (запланированным на июль 2017-го).
Немногие разбираются в этой теме настолько хорошо, как Дмитрий Чуйко (Oracle) и Никита Липский (Excelsior JET). Дмитрий, работающий в команде Java SE Performance, участвует в проекте AOT, и выступал с докладом о нём ещё весной на JPoint 2016 (поскольку работа над JEP ещё идёт, на все слова Дмитрия об этом распространяется стандартный дисклеймер Oracle «компания ничего не обещает», но рассказать он может уже много интересного).
А Никита ещё с 1997 года занимается статической компиляцией Java (проект Excelsior JET известен как раз этим), и ранее на Joker рассказывал об AOT в случае с Java.
Поэтому мы обсудили сразу с обоими и цели добавления AOT в HotSpot, и практическое значение этой новости для Java-разработчиков, и различие AOT в HotSpot и Excelsior JET. В разговоре выяснились любопытные детали вплоть до того, какая дополнительная цель скрывается за обтекаемой формулировкой из JEP.
JUG.ru: Сначала давайте уточним для не следивших за темой, что именно произошло.
Дмитрий Чуйко: Затаргетировали ту реализацию AOT-компиляции в HotSpot, которая предполагалась. Это ограниченный proof of concept, пока что предназначенный для компиляции только стандартного модуля java.base и существующий на правах эксперимента. Но, тем не менее, это фича продуктового качества. О которой известно, по меньшей мере, что можно для одного стандартного модуля получить статически скомпилированный нативный код методов, и в дальнейшем переиспользовать его при старте вашего приложения, что может ускорить этот старт.
Эта фича была назначена в релиз Java 9, и доступно публичное ревью, которое можно посмотреть. И, видимо, в ближайшее время будет доступен какой-то билд, позволяющий попробовать. В принципе, при желании можно взять патчи из ревью и применить у себя.
Понятно, что на этом дело не остановится, просто с чего-то надо начинать. То, что сделано — не то чтобы маленький, но минимальный базис, чтобы в дальнейшем делать разные вещи, связанные со статической компиляцией.
Никита Липский: Замечу, что java.base — не единственное ограничение: первая версия будет доступна только для Linux/x64-платформы. Но это на сегодняшний день самая популярная серверная платформа, так что можно будет там, по крайней мере, попробовать, даёт ли вам AOT в HotSpot какое-нибудь счастье.
JUG.ru: Когда до релиза Java 9 остаётся меньше года, не ожидаешь добавления принципиально нового. Почему это произошло сейчас — помог недавний перенос релиза?
Дмитрий: Фича достигла того продуктового качества, соответствия требованиям, когда её можно стало брать в релиз. И время релиза оказалось подходящим, чтобы это сделать. Да, думаю, что если бы Java 9 не оказалась отложена, то AOT-компиляция в релиз не попала бы. По крайней мере, в первый. На тот момент она была не готова. Можно, конечно, интегрироваться раньше и на ходу отлаживать баги, но это заставило бы страдать всех, кто использует основной репозиторий.
JUG.ru: Но она же opt-in, то есть её можно просто не включать, почему тогда страдали бы?
Дмитрий: Это и было одной из целей, которых мы хотели достигнуть до включения AOT в релиз: реализация должна быть такой, чтобы у тех, кто её не включает, ничего не менялось. При таких больших изменениях риски, что сломается что-то у тех, кто их не использует, большие. Более того, такого рода проблемы находились по ходу разработки.
JUG.ru: Кстати, про opt-in — так всегда будет, или в будущем AOT может стать опцией по умолчанию?
Дмитрий: Я не думаю, что это будет опцией по умолчанию. Просто потому что нельзя «скомпилировать всё».
Никита: Хотя бы java.base может быть по умолчанию включен?
Дмитрий: Можно это делать, но я не думаю, что так будет. Хотя бы в силу того, что в любом случае получится бинарник больших размеров, какой бы реализация ни была. Бинарник даже для java.base — достаточно большой артефакт, который не все захотят у себя иметь.
JUG.ru: Предположим, гипотетический разработчик узнал, что в HotSpot добавляют AOT ради улучшения времени запуска приложения, и хочет без вникания «как это работает внутри» понять, что лично ему это даст. Что вы ему можете сказать?
Дмитрий: Скажем так. Первое, с чем столкнётся разработчик — ему самому всё равно будет нужно что-то компилировать. Workflow немножко изменится: у него был просто какой-то код (в виде модулей, JAR’ов или даже class-файлов), и к этому нужно добавить в самом простом случае получение каких-то нативных библиотек (при деплое или любом другом действии, предшествующем запуску), даже для java.base. Когда нужно будет вспомнить, с какими флагами намереваются запускаться, на каком железе. И, как я вижу, даже в самых простых случаях всё приходит к тому, что человек помнит про используемые флаги и переходит к подкручиванию настроек.
К чему я так долго веду? Дело в том, что сценарии очень различаются для разных приложений. Есть очень большая разница между вариантом, когда используют по максимуму ресурс процессора для разогрева приложения, и когда просто в одной нитке крутятся и что-то делают. И сразу оба этих различающихся варианта не будут хорошо работать из коробки до настройки. Сценарии бывают взаимоисключающими, так что однозначного рецепта, который сделает лучше, нет.
Поэтому, когда Java 9 выйдет, можно будет сначала просто посмотреть, что работает и не работает. А следующим шагом точно станет подкручивание флажков, которые есть уже сейчас, на самом деле это флажки Graal.
Никита: В вопросе «что это даст» надо рассматривать отдельно холодный старт, когда приложение стартует сразу после загрузки ОС, и тёплый, когда все файлы приложения в дисковых кэшах.
В случае с холодным стартом проблема медленного запуска может с AOT не решаться, а усугубляться: теперь надо ещё больше грузить данных с диска, потому что нативный код в общем случае ещё больше, чем байткод. Мы в Excelsior решаем проблему холодного старта с помощью профилировки старта, определяя код, исполняющийся на старте, и отправляя его в начало исполняемого файла. Похожую оптимизацию, кстати, реализовали в Java 9 в утилите jlink на уровне байткода модулей. А вот когда такая же оптимизация появится в HotSpot AOT — непонятно, и без неё холодный старт приложений с AOT может получиться даже хуже, чем без AOT.
А вот тёплый старт приложения AOT улучшает, потому что не нужно байткод интерпретировать, профилировать, JIT-компилировать. Но когда вам нужен тёплый старт? Грубо говоря, когда запускаете много маленьких batch-утилит подряд, для которых время прогрева JVM может быть больше, чем время исполнения этих программ. А у больших приложений старт обычно холодный. Кроме того, у больших программ загрузка платформенных классов на старте — это лишь малая часть от классов самого приложения. Так что, когда у вас есть только предкомпилированный java.base, вы можете не заметить никаких улучшений даже в случае тёплого старта.
Поэтому мне лично кажется, что может получаться ощутимый выигрыш, когда нужно запускать много короткоживущих процессов, а про большие серверные приложения лично у меня есть сомнения, что там будут какие-то выигрыши. Но каждый, конечно, сможет сам попробовать.
Дмитрий: Сценарий «тёплый старт больших серверных приложений» не такой уж нереалистичный — по крайней мере, при девелопменте, отладке могут быть рестарты. А к тому, что сейчас в AOT чего-то нет (профиля старта, например) — так теперь это станет можно добавить! Потому что теперь появляется базовая функциональность, которую дальше можно наращивать.
А вообще, если вспомнить мой доклад, то у AOT в HotSpot несколько целей, не только уменьшение времени запуска. Оно просто упомянуто в JEP…
Никита: Как главная цель!
Дмитрий: На самом деле, как наиболее измеримая цель. И явно подразумевается, что при этом в пиковой производительности приложения не должны терять (или терять редко и не сильно).
А есть ещё и такой жизненный сценарий, как работа JVMCI-компиляторов (сейчас известен один — Graal), когда во время старта приложения нам не нужно компилировать сам компилятор. Хотя раскрутка именно компилятора может происходить при помощи C1, тем не менее, это некоторые ресурсы, когда мы не можем скомпилировать код приложения, а занимаемся тем, что перекомпилируем компилятор, хотя про него и так всё известно. Можно заранее взять и получить для него нативный код.
Никита: То есть официально вы будете поддерживать java.base, но Graal вы тоже тестируете, и он будет работать в AOT-скомпилированном виде?
Дмитрий: В основном работа идёт с java.base, но в JDK 9 даже он официально поддерживаться не будет, вся фича экспериментальная. А Graal тоже тестируем. Оговорюсь: это работает не во всех комбинациях, не было заявлено, что это поддерживается, но это действительно работает. И AOT в этом случае помогает.
Никита: Ну понятно, что для бутстрэпа Graal лучше его скомпилировать до запуска. У нас в Excelsior JET тоже JIT написан на Java, и, соответственно, наличие AOT позволяет его не бутстрэпить им же самим, мы его сразу же переводим в машинный код.
JUG.ru: Никита, а расскажите подробнее про AOT в Excelsior JET: чем вы отличаетесь от того, что теперь появится в HotSpot?
Никита: У нас AOT с первого дня, это основная фича нашего продукта. Мы компилируем всё приложение пользователя в машинный код до исполнения, и пользователь распространяет своё приложение в виде исполняемого файла целевой платформы.
Конечно, так как мы совместимая с Java SE-спецификацией реализация, у нас есть и динамический компилятор тоже. Те классы, которые появляются только при исполнении (например, порождаются фреймворками), обрабатываются им. Но у нас динамически компилируются только те классы, которые во время статической компиляции неизвестны. Соответственно, мы в динамическую компиляцию никогда особенно сильно не вкладывались, а вместо этого делали очень много статических оптимизаций, и считаем это вполне хорошим подходом, имеющим право на существование.
А в HotSpot изначально во главу угла ставилась динамическая компиляция, и там как раз появляющийся AOT-компилятор будет опциональным дополнением. В этом принципиальная разница. Впрочем, вот сейчас мы улучшаем динамический компилятор, вкладываемся и в производительность порождаемого им кода тоже, а у Oracle появляется AOT — получается, что по факту идём к одному с разных концов.
Дмитрий: Да, я совершенно согласен. Для HotSpot это такая добавочка, «приправочка». Если не смогли какие-то методы скомпилировать статически — не беда. Если у нас нет вообще статически скомпилированных методов — не беда. Скомпилировалось слишком много — тоже не проблема. Это просто средство решить, возможно, некоторые проблемы пользователя с производительностью добавлением статически скомпилированного кода. Ещё эта «дополнительная возможность» потенциально открывает двери к тому, чтобы реализовывать другие фичи. Это начало очень длинной дороги.
Мы действительно идём к одному и тому же с разных концов. Не факт, что мы достигнем той точки, где возможности будут одинаковыми. И хорошо, если не достигнем, это более логично. Потому что разным пользователям нужно разное.
Никита: Ну и мы не единственные, кто занимается AOT. У IBM и Azul Systems тоже есть AOT-компиляторы, также используемые для улучшения старта. Если правильно помню, наиболее часто используемые стандартные платформенные классы у них скомпилированы AOT по умолчанию.
JUG.ru: Поскольку у вас настолько разный подход, появление AOT в HotSpot никак не скажется на востребованности Excelsior?
Дмитрий: Я думаю, что именно так.
Никита: Да, у нас сохраняется принципиальная разница. Одним из основных selling points Excelsior JET сейчас является защита кода от декомпиляции. Известно, что Java-байткод легко превратить в исходный код с помощью декомпиляторов, а вот превратить оптимизированный машинный код во что-то, что можно легко читать и исправлять, практически невозможно. В нашем случае оригинальный байткод после компиляции, как правило, становится просто ненужным, и вы распространяете своё приложение в чисто нативном коде.
Насколько нам известно, другой совместимой реализации Java SE, обладающей такой возможностью, нет. И, в частности, распространение приложения, скомпилированного с помощью HotSpot, на данный момент не предполагается без байткода. Потому что производительность достигается с помощью tiered AOT, перекомпиляции горячего кода оригинального байткода.
Если когда-нибудь Oracle решится дать возможность распространения приложения без байткода, в виде non-tiered AOT, тогда это действительно составит нам конкуренцию (и это хорошо, мы считаем, что конкуренция нам тут не повредит). Но в этом случае Oracle придётся довольно серьёзно вложиться в производительность этого non-tiered AOT, которая сейчас, прямо скажем, слабая. Ну и, мне так кажется, Oracle не верит, что с помощью чистого AOT вообще можно достичь хорошей производительности. Поэтому сейчас единственная цель, которую Oracle прямо ставит в JEP 295 — startup time.
Дмитрий: Я не согласен с Никитой в том, что non-tiered AOT такой уж плохой. Да, не сказать, что он супер-оптимальный, но это хороший нативный код. По крайней мере, намного-намного лучше интерпретатора. Действительно, даже в этом варианте он требует наличия оригинального байткода, оригинальных классов, которые всё равно грузятся из class format’а, то есть это такое дополнение.
Да, мы можем для части данных классов использовать Application Class Data Sharing, так же статически превратить в некоторые внутренние бинарные форматы и использовать. Но опять же, для их загрузки нам всё равно нужны оригинальные классы. Поэтому это by design поведение HotSpot, в которое мы просто добавили возможность получить от него что-то ещё. Побыстрее загрузить классы, побыстрее получить нативные версии методов — это всё хорошо.
Никита: У меня есть такой вопрос к Дмитрию. Я слышал, что была идея сделать AOT коммерческой фичей, а теперь он получается доступным всем. Правильно ли понимаю, что от этой идеи отказались?
Дмитрий: Да, это открытая фича, базовая реализация полностью открыта. Ревью уже появились, ничего уже не спрячешь. Но можно коммерциализировать различные сценарии на её основе, вариантов может быть много. Как в том же Excelsior — когда не просто компилятор, а можно по-хитрому скомпилировать, чтобы было ещё лучше.
Никита: Ясно. Ещё есть вопрос о другом: войдёт ли AOT в стандартные дистрибутивы JDK от Oracle для Linux, или будет доступен только для самостоятельной сборки в OpenJDK?
Дмитрий: О, крутой вопрос. Я, честно говоря, не знаю, и я даже не уверен, что это решалось. Но пока вроде как да.
Никита: Будет прямо в дефолтной сборке? Скомпилированный java.base — это же довольно серьёзное увеличение footprint JRE/JDK.
Дмитрий: Нет, скомпилированного java.base там не будет. В первую очередь из-за того, что в текущей технической реализации это непереносимый артефакт, и он очень большой. И я не уверен, что в подавляющем большинстве сценариев это вообще нужно пользователю. Чтобы запустить простое приложение, не требуется полная .so-шка java.base, там количество вызываемых методов существенно меньше.
Никита: Хорошо, а сам AOT-компилятор в стандартный JDK войдёт, пользователь сможет сам скомпилировать java.base для себя?
Дмитрий: Да, причём войдёт не только AOT-компилятор, сам инструмент, но ещё и Graal. Graal используется для статической компиляции, но может быть использован и как динамический компилятор последнего уровня, что тоже интересно. Сам этот компилятор написан на Java. И вообще подход Java on top of Java обозначен как одно из направлений развития платформы.
Никита: А Graal войдёт во все дистрибутивы, или тоже только для Linux?
Дмитрий: Пока только для линукса. Интересный комментарий: одно из первых писем в рассылке, когда появилось ревью: «Скорее выкладывайте, мы начнём как можно быстрее портировать на ARM64». То есть хорошо, что это будет открытый код, потому что для любой платформы разработчики смогут портировать его, и получить для неё статическую компиляцию. И результаты для других платформ, я думаю, могут быть интереснее в плане перфоманса.
Никита: Понятно. К тому, что «скомпилированный java.base очень большой»: а насколько большой? Я по докладу JVM Language Summit 2015 помню, что около ста…
Дмитрий: Где-то 265.
Никита: Чего?
Дмитрий: Мегабайт.
Никита: ДВЕСТИ ШЕСТЬДЕСЯТ ПЯТЬ МЕГАБАЙТ?
Дмитрий: Да.
Никита: И как вы тогда собираетесь улучшать время запуска, если вам при запуске требуется такая огромадная so-шка?
Дмитрий: Ответ простой. Эта «огромадная so-шка» содержит в основном read-only данные, shared-часть больше половины. И на самом деле в случае большого количества инстансов, когда мы действительно шарим эту память, размер не так важен. На холодный стартап это несколько влияет, но холодный стартап с HotSpot всё равно очень плохой, и загрузка каких-то существенных частей большого файла не ухудшает ситуацию сильно. Всё те же примерно десятки секунд на не очень сильной машине.
Никита: Ну то есть всё становится ещё хуже, чем было.
Дмитрий: Конечно, становится несколько хуже в плане загрузки какого-то количества данных с диска, но ненамного. А вот в случае стартапа с прогретой файловой системой это не играет роли. Сам файл находится в файловом кэше ОС, возможно, сама библиотека загружена. Это не страшно — иметь большую so-библиотеку. Есть случаи, когда это нежелательно, но само по себе это не плохо.
Никита: Ну окей. Я прокомментирую: с моей точки зрения, 265 мегабайт — это чересчур много. Например, в нашей реализации java.base со всей нашей JVM внутри занимает порядка 30 мегабайт. При этом в нашей реализации вам не нужны платформенные классы в виде байткода в дистрибутиве своего приложения.
Дмитрий: Это все-все методы всех классов, с private API?
Никита: Да, со всеми private API, агрессивным инлайном, метаинформацией для reflection, своей собственной метаинформацией (например, таблицами исключений, таблицами виртуальных методов), и так далее. В следующем релизе ещё подужмём: выкинем таблицы виртуальных методов, будем их порождать в рантайме.
Мне поэтому и удивительно, как вы умудрились получить 265 мегабайт. Хотя не очень удивительно. В нашей первой бете метаинформация занимала 80% в бинарнике — мы её потом оптимизировали в 4 раза и продолжаем в эту сторону работать.
Ну а когда по дизайну HotSpot AOT, кроме этой огромной 265-мегабайтной so-шки, нужен ещё и весь байткод, то есть весь rt.jar должен присутствовать… Понятно, что в модулях Java 9 он будет распилен на кучу других jar-ов, но это всё нужно будет. То есть HotSpot AOT — это на данный момент такой чистый оверхед на disk footprint, причём неимоверно здоровый.
Ещё такой вопрос. Я по тому же JVM LS 2015 помню ещё, что вы не умеете инлайнить код. Вы с тех пор научились его инлайнить в AOT-режиме?
Дмитрий: Да, есть некоторый инлайнинг, но поскольку не используется информация, доступная во время исполнения, то в любом случае это такое «пальцем в небо».
Никита: У вас со 100 мегабайт до 265 увеличилось после того, как научились инлайнить?
Дмитрий: Было некоторое увеличение из-за этого, но сильнее повлияло не оно. В частности, существенный объём занимает код, связанный с профилированием. То есть это размер для tiered AOT.
Вот, кстати, некорректное сравнение получается: «30 мегабайт — хорошо, 265 — плохо». Это, конечно, хорошо, что 30, но в них нет профилирования, а в 265 профилирование есть. Когда мы можем перейти в level 3 и потом скомпилироваться в C2, имея тот же профиль, который раньше имели в HotSpot с обычной tiered-компиляцией. Конечно, размер без поддержки tiered-компиляции существенно меньше. Ну и есть некоторая вариация посередине, когда мы можем для части методов профилирование не делать.
Никита: Окей, понял, то есть это профилировка занимает место, спасибо. А вот о другом: я в JEP между строк нашёл ещё одну цель, хочу это тоже обсудить. Там можно чёрным по белому прочитать:
«The logical compilation mode for java.base is tiered AOT since JIT recompilation of java.base methods is desired to reach peak performance. Only in certain scenarios does a non-tiered AOT compilation make sense. This includes applications which require predictable behavior, when footprint is more important than peak performance, or for systems where dynamic code generation is not allowed.»
Самое интересное в этом абзаце: «for systems where dynamic code generation is not allowed». Что это за системы? На данный момент есть ровно одна такая популярная система — это iOS. С появлением iOS главный лозунг Java «Write once, run anywhere» перестал соответствовать действительности. И технически эту проблему можно решить только с помощью AOT-компилятора.
В принципе, конечно, для iOS есть JVM из проекта OpenJDK Project Mobile с интерпретатором Zero, но понятное дело, что производительность такой JVM для iOS крайне неудовлетворительна. Кроме того, в HotSpot есть (будет в Java 9) поддержка pregenerated template interpreter, который работает на IOS — производительность выше Zero, но всё ещё неприемлема для промышленного применения.
Есть и другие компании, пытающиеся сделать Java для iOS. Компания Gluon недавно анонсировала свой AOT для iOS в составе Gluon VM, которая основана на Project Mobile. Пока не понятно, насколько AOT от Gluon порождает производительный код, тестов производительности они пока не анонсировали. Были и другие попытки — почивший RoboVM или Intel Multi-OS Engine.
Мы, конечно, тоже думаем сделать Java для iOS, первый шаг недавно сделали — выпустили Excelsior JET для Linux ARM. Так вот, мне кажется, что AOT от HotSpot тоже эту цель преследует. Дима?
Дмитрий: Конечно, хочется присутствовать на всех платформах, особенно на популярных. Так что цель действительно эта: ну не написаны эти три буквы, но ты прочитал правильно! Именно это имеется в виду, и при текущем решении, если к нему добавить поддержку архитектуры AArch64, это будет жить. Да, рантайм будет большим, количество кода большое, но фактически это будет работать. Всё есть — виртуальная машина, среда исполнения, нативный код, из которого можно никуда не переходить, а оставаться в нём…
Никита: В этом случае вам придётся-таки вложиться в уменьшение размера.
Дмитрий: Задача оптимизации — следующий шажок на длинном пути. Можно дать поддержку архитектуры, и уметь работать без того, этого, третьего… Всё должно получиться.
Сейчас уже можно увидеть много кусочков мозаики, которые появились и развиваются в разных местах Oracle JDK. Такие, как modules, jlink, AppCDS, AOT. Технически уже можно для приложения автоматически выделить ровно те модули, которые ему нужны, и пре-генерировать по ним архивы с данными классов и библиотеки с нативным кодом, динамический байткод, интерпретатор. Эти артефакты можно переиспользовать от запуска к запуску и шарить между одновременно запущенными инстансами.
JUG.ru: Java и iOS — неожиданное сочетание, насколько велик спрос на такое?
Дмитрий: Спрос есть на то, чтобы write once, и дальше во все магазины приложений.
Никита: Есть же купленная Microsoft компания Xamarin, которая позволяет писать на C# и на iOS, и на Android. Спрос на многоплатформенные приложения действительно есть. У разработчиков есть проблема поддержки нескольких кодовых баз для разных платформ. Решение, позволяющее избежать этого (без необходимости переходить на низкий уровень C или, наоборот, чересчур динамический уровень JavaScript с его проблемами производительности), было бы очень полезно.
JUG.ru: Спасибо за ответы. Хочется что-нибудь добавить напоследок?
Никита: Закончим на оптимистической ноте: и Дима, и я рады, что AOT в HotSpot появится! В девятке он будет не идеальным, но со временем может развиться в то, что будет полезно широкому кругу пользователей. Я лично этому рад, потому что всегда была проблема убедить общественность, что AOT для Java может быть полезен и вообще возможен. А теперь в этом будем убеждать не только мы, но и Oracle.
Дмитрий: А меня особенно радует, что это действительно open-вещь. То есть интересно это будет широкому кругу не только пользователей, но и разработчиков, которые смогут посмотреть, как это сделано. И добавить что-нибудь полезное!