Сергей Куксенко — перформанс-инженер, видевший Java еще версии 1.0. За это время успел поучаствовать в разработке мобильных, клиентских, серверных приложений и виртуальных машин. Производительностью Java занимается c 2005 года и в данный момент в Oracle работает над улучшением производительности JDK. Один из самых популярных докладчиков на Joker и JPoint.
Этот хабрапост — большое интервью с Сергеем, посвященное следующим темам:
- Культ Производительности;
- Когда и что нужно оптимизировать, изначальный дизайн языка и библиотеки;
- Перспективные направления для дальнейшей оптимизации;
- Как можно поучаствовать в разработке и что можно сломать оптимизациями;
- Компиляторные трюки, размещение регистров;
- Можно ли собрать кошку из фарша;
- Когда тесты работают пять дней подряд и прочая бытовуха;
- Как стать перформанс-инженером;
- Подготовка доклада на следующий Joker.
О культе Производительности
Олег: Ты наш давний докладчик, и это не первое наше интервью. Расскажи немножко, кто ты сейчас, чем ты занимаешься?
Сергей: Я тот же самый, что и много лет назад, и занимаюсь тем же самым. Я работаю в команде Java Performance и отвечаю за производительность Java-машин Oracle, OpenJDK.
Олег: Тогда у меня такой, несколько тролльский вопрос: вот ты перформанс-инженер, и доклады твои про всякую производительность. Не кажется ли тебе, что вопрос производительности несколько переоценен? Все с ней носятся, а это вообще нужно?
Сергей: Это хороший вопрос. Всё зависит от другого. Подобное внимание аудитории можно считать излишним. С другой стороны, производительность с точки зрения бизнеса — это деньги.
Это реальные деньги, которые люди тратят на железо, на какие-нибудь облака в Amazon. Если ты не обрабатываешь свои запросы достаточно быстро — всё, ты теряешь клиентов, теряешь деньги, теряешь всё остальное. Поэтому запрос на производительность, конечно же, всегда есть. Вопрос в том, насколько он является важным в каждом конкретном случае. Я уж молчу про high-frequency trading.
Олег: Кстати, как думаешь, Java подходит для этого?
Сергей: У тебя была возможность встречаться с таким человеком, как Питер Лори (Peter Lawrey)?
Олег: Это который CEO Chronicle Software, разработчики OpenHFT?
Сергей: Это очень известный товарищ из Лондона, который много ездит по конференциям. Они работают на Java в high-frequency trading, живут прекрасно.
Олег: Они именно на Java это делают или из Java зовут нативный код? Всё-таки разница есть.
Сергей: Я на таком уровне не знаю, он это не рассказывал. В принципе, при желании, всего, чего нужно, можно достичь на самой Java.
Олег: Интересно. Если взять, к примеру, какое-нибудь сообщество питонистов, то у них куда меньше культ производительности. Как получилось, что именно в нашем сообществе так происходит? Может, это вы спровоцировали культ перформанса своими докладами? Ты, Шипилев, Паньгин, Иванов и так далее.
Сергей: Не знаю, как так получилось. Культ производительности на российской конференции заметно выше, чем на американской. Может быть, это отражает саму аудиторию. У нас люди хотят больше заниматься производительностью, им это интересно. А в той же Америке больше хотят заниматься тем, за что больше платят. Но это гипотеза, догадки. Так сложилось.
Когда и что нужно оптимизировать
Олег: Ты сказал, что запрос на перформанс всё равно есть. В какой момент нужно начинать задумываться о производительности? Когда гром грянет?
Сергей: Это общий абстрактный вопрос. Лучше ещё раз обратиться к кейноуту Алексея Шипилёва с одной из прошлых конференций, где он достаточно хорошо всё это расписал.
Олег: Да, я помню «кривую имени Ш».
Сергей: Заниматься перформансом нужно сразу, но смотря на каком уровне. Необязательно сразу писать бенчмарки. Известно, например, что банальное ограничение уровня архитектуры API между Set’ом как множеством и SortedSet уже накладывает на нас фундаментальные алгоритмические ограничения.
Если мы в API запихнули SortedSet (хотя то, что он отсортированный, никому не нужно), а потом это расползлось по всей нашей системе, то потом эту штуку придется вырывать больно и трудно.
Вопрос начинается с самого уровня дизайна — это вопрос минимальных ограничений. Нужно использовать наименьшие возможные ограничения, чтобы потом с ними можно было играть. Например, когда я крутил различные куски Java, то на ум приходили исключительно нехорошие слова. Я хотел бы что-то сделать с одним из базовых классов, а ничего не могу, потому что API зафиксирован, его уже не поменяешь, он уже выполз наружу. Но для того, чтобы сделать какую-то хитрость и разгон, требуется скрыть некоторые детали.
Конкретный пример: я в свое время приседал вокруг класса java.math.BigDecimal. Был большой реквест с разных сторон его как-то разогнать. Есть достаточно хороший, частный случай, когда у нас BigDecimal нифига не «Big», он просто Decimal, и нужно их читать.
Сейчас, конечно, для этого сделана соответствующая обертка. Но если бы из BigDecimal не торчал наружу публичный конструктор, а были бы какие-то статические методы и фабрики, то можно было бы сделать BigDecimal абстрактным, и наружу выплёвывать две разные реализации, которые был работали, как им нужно. Но это невозможно, потому что торчит конструктор. Из-за этого приходится уже внутри делать ненужный runtime check, который позволяет идти по быстрому пути в ряде случаев.
Олег: Следует ли из этого, что при разработке стандартной библиотеки стоит отказаться от конструкторов и везде делать билдеры?
Сергей: Уже поздно.
Олег: Если бы не было поздно, это была бы хорошая идея?
Сергей: Она бы дала больше пространства для манёвра. Вот смотри: мы пишем new, и это new стоит снаружи конструктора. Получаются две операции: сначала создаем объект, затем вызываем конструктор, который его заполняет. А иногда было бы очень полезно спрятать само создание объекта и создавать не тот объект, который у нас снаружи. Это языковое ограничение, изначальное, с первых времен Java.
Олег: Ну, сейчас все пользуются DI-фреймворками, которые позволяют крутить прокси как хочешь и добавлять что угодно, обходя это ограничение. В изначальном дизайне языка можно было бы что-то такое добавить, встроенный dependency injection container?
Сергей: У меня есть очень конкретное мнение по поводу изначального дизайна языка. Если вспомнить историю выхода Java 1.0, она выходила достаточно серьезным time pressure, нужно было все было сделать быстро.
Существуют тысячи вещей, которые я лично хотел бы видеть исправленными с самой первой версии. Но боюсь, что если даже из этой тысячи выбрать одну-две-три, и их начали бы делать во времена выхода первой Java, то Java бы не вышла. Это стандартный пример того, что лучшее — враг хорошего.
Что ещё можно оптимизировать в Java
Олег: Обычные люди могут поправить что-то только в своем проекте, а вы как перформанс-инженеры JDK воздействуете сразу на сотни тысяч проектов. Возникает вопрос: за более 20 лет развития Java остались ли в JDK какие-то области, вмешательство в которые core-инженеров может привести к заметному эффекту? И насколько заметен этот «заметный эффект»?
Сергей: Во-первых, сейчас Java работает совсем не на том железе, что, скажем, 10 лет назад. Железо сейчас и железо 10 лет назад — это две большие разницы, и желательно делать различные оптимизации.
Во-вторых, это, конечно, замечательно, когда performance-инженер сидит и что-то разгоняет, получает огромные цифры, репортит своему начальству, выбивает деньги на премию после этих разгонов. Но огромная масса работы идет по новым проектам. Добавляется фича, и задача performance-инженера не разогнать фичу, а убедиться, что в этой фиче всё ок. Или если не ок, то придумать какое-то исправление.
Олег: А как можно убедиться? Вы же не верифицируете код формально. Что такое «убедиться»?
Сергей: Убедиться, что всё ОК с точки зрения перформанса — это субъективное экспертное мнение performance-инженера, который напишет отчет и скажет, что «в этой фиче всё нормально». В зависимости от размера фичи это подразумевает под собой иногда совсем немножко действий, иногда много разных усилий. Начиная с того, что надо просто тупо сидеть, смотреть, что там делается, забенчмаркать эту область, погонять бенчмарки, посмотреть, что там получается на выходе, и принять разумное обоснованное решение.
Олег: А с точки зрения перформанса и новых фич — Java вообще вперед двигается? Там есть что-то? Потому что железо у нас не сильно изменилось, если, например, об Intel говорить.
Сергей: За какой это период не изменилось?
Олег: Например, последние 10 лет.
Сергей: Да, на железе десятилетней давности есть AVX-512?
Олег: Нет. Он, наверное, и на современном не везде есть?
Сергей: У меня точно нет. У нас в лабе есть, но это всё оккупировано компиляторщиками. Они пока прикручивают, поэтому я ещё не посмотрел.
Олег: Можно ли считать поддержку AVX-512 примером типичной фичи?
Сергей: Наверное, можно. Чем конкретно я занимаюсь: у нас был большой пласт работ по тому, что есть современные требования по добавлению новых криптоалгоритмов. Это вещь, где на алгоритмы криптографии десятилетней давности просто нельзя полагаться. Нужны новые алгоритмы, более бОльшие ключи. И добавление новых криптоалгоритмов происходит, я бы сказал, постоянно.
Олег: Они как-то аппаратно ускоряются?
Сергей: Всё зависит от конкретных алгоритмов. Есть очень хорошо ускоренные алгоритмы. Кстати, 10 лет назад это не заработало бы на железе Intel, но лет 5-6 как появились хорошие инструкции, вплоть до AES-блоков с ускорениями. Все это было реализовано с минимальным временным интервалом.
Олег: Что насчет GPU, они же тоже матрицы умеют перемножать?
Сергей: Про GPU — отдельный разговор. У нас есть для этого есть проект Panama, в котором ведутся все эти работы, и когда-нибудь он дойдет и до мейнлайна Java со всеми плюшками.
Олег: У меня есть пара знакомых, которые занимаются, условно, финансовой математикой. Начиная с какого-то момента они всегда переходят на С++ для вычислений и утверждают, что из managed-платформы очень неудобно все эти оптимизации и аппаратные штуки использовать. Можно ли это улучшить?
Сергей: У нас тоже есть большой запрос на это и есть ряд внутренних требований. Например, сделать что-то, чтобы в области machine learning всё работало лучше. Как правило, это банальное матричное перемножение, которое можно скидывать на GPU. Работы по этому ведутся, скажем так.
У нас есть два больших проекта-«зонтика»: Valhalla и Panama, которые должны собрать фичи вроде GPU. На стыке Valhalla и Panama сидит vector API, который работает с нашими инструкциями SIMD/SSE/AVX, напрямую из Java-кода, и сама Valhalla с inline-типами — это всё большие шаги в ту сторону.
Что можно сломать оптимизацией, как поучаствовать в разработке
Олег: Упомянутые тобой зонтики похожи друг на друга. Не бывает ли так, что один проект влияет на другой, в том числе, с точки зрения кода и профиля производительности? Например, ты у себя что-то отрефакторил, а несчастный Рон Пресслер, обливаясь слезами, в уголке вечером чинит свои тесты?
Сергей: Такое бывает постоянно. Конкретный пример — Vector API. Для того, чтобы Vector API хорошо работал, наши нативные вектора должны в конце концов стать value-типами, или как сейчас это называется в Java, inline-типами. Можно сделать обходной маневр в hotspot и как-то это реализовать, но хочется иметь и общее решение. С другой стороны, ключевая фишка inline-типов как раз в том, чтобы не запариваться с layout-ом этих данных, а layout этих данных для Vector API предельно важен.
Потому что он, на самом деле, напрямую соответствует AVX-512 и всему такому. Понятно, что нужно сделать некоторые приседания, некоторые оптимизации, которые, с одной стороны, сделают inline-тип обычным типом, но у которого будет аппаратно завязанный layout. Естественно, возникают пересечения. Если посмотреть на группы людей, которые двигают Panama и двигают Valhalla, они больше, чем наполовину пересекаются.
Олег: Чисто организационно, вот у тебя есть проект, какая-то проблема с перформансом, а она находится в стыке нескольких проектов. Что делать дальше? Как это решать? Получается, это уже trade-off между проектами и людьми, а не между какими-то абстрактными задачами.
Сергей: Здесь всё очень просто: если это перформанс-проблема с фичей, которая только дизайнится, надо пойти к людям, которые дизайнят, и сказать — «так-то и так-то, что будем делать? Давайте сделаем по-другому». Начинается обсуждение, и проблема решается.
Если же код уже существует — он уже работает. В идеальном случае ты чинишь эту проблему, или, если не можешь починить полноценно, — ляпаешь прототип, потом опять приходишь к владельцу кода и говоришь: «Вот прототип, что будем делать?» Дальше мы этот вопрос решаем конкретно по каждому случаю.
Олег: У нас тут есть заинтересованные лица, которые не могут в этом процессе поучаствовать, это конечные пользователи.
Сергей: Они не могут поучаствовать ровно настолько, что им за это не будут платить зарплату в Oracle. Если зарплата не нужна — приходите в OpenJDK и участвуйте.
Олег: Насколько это вообще реально? В OpenJDK сидят какие-то чертовы гении типа тебя, и где обычные люди, и где вы. Допустим, у меня что-то тормозит, что и как я должен сделать?
Сергей: Если ты не знаешь проблемы, это отдельный вопрос, будет ли кто-то заниматься за тебя поиском решения, это вопрос как области, примера и так далее. Даже если не знаешь проблему, имеет смысл, может быть, написать на OpenJDK и спросить. Если это что-то такое, у кого-то сразу щёлкнет в голове, народ за это схватится. Если это никому не интересно, это будет висеть без ответа.
Олег: Допустим, я знаю проблему и даже знаю, что надо поправить.
Сергей: Если ты знаешь проблему, ты приходишь в OpenJDK, подписываешь все нужные бумажки, предлагаешь патчик, его ревьят и заливают.
Олег: Вот так просто?
Сергей: Ну да, немножко бюрократии, немножко подождать. Вот вчера Тагир (lany) у меня подхватил один небольшой фикс, который я забросил. Ему вот хочется, чтобы его довели до конца. Он стал его самостоятельно доводить до ума. Говорит: «Блин, ну что такое, я вот всё сделал, выложил, никто не ревьюит». Ну да, никто не ревьюит. Сейчас июль, половина джавовского офиса в отпусках. Вот выйдут из отпусков и проревьюят.
Олег: В США отпуска примерно в те же даты, что обычно в России?
Сергей: Нет, в США система отпусков совершенно не такая, как в России. Во-первых, они существенно меньше. А еще, в США система отпусков завязана на школы. Когда у тебя дети на каникулах — тогда и отпуска. Как только начинаются каникулы, вся Америка приходит в движение. А поскольку занятия здесь заканчиваются в середине июня и начинаются в середине августа, то эта дельта для отпуска не такая уж большая — всего два месяца.
Компиляторные трюки, размещение регистров
Олег: Бывало ли так, что вы что-то оптимизировали у себя, а пользователям после этого пришлось по-другому код писать? Условно говоря, если операция выделения подстроки раньше брала диапазон, а теперь делает полную копию, то этот рефакторинг меняет способ написания кода.
Сергей: Наверняка такое было, но не возьмусь сейчас приводить конкретные примеры. Вопрос, под что закладываются люди при написании кода. Если им надо выжимать максимальный перформанс, и они для этого делают всякие зависящие от компилятора трюки — они должны быть готовы, что компилятор эволюционирует со временем, и им придется постоянно модифицировать свой код в соответствии с текущим состоянием компилятора. И это ещё замечательно.
Допустим, вдруг через 20 лет придёт Graal как основной компилятор для HotSpot — тогда этим бедным ребятам придётся переписать вообще всё. Это происходит только если ты взял на себя такой вот технический долг — отслеживать изменения в компиляторе. Гораздо проще писать правильный код без прямых завязок, с более-менее нормальными общими реализациями.
Кстати, про компиляторы — не только про компиляторы Java, а вообще. Есть закон Мура, который нифига не закон, а просто эмпирическое наблюдение о том, что количество транзисторов удваивается каждые полтора года.
А есть точно такой же закон (Proebsting's Law) о том, что производительность кода без его модификации увеличивается на 4 процента каждые полтора-два года. Эти 4 процента — то, что конечные пользователи получают на халяву просто от эволюции компиляторов. Не железа, а именно компиляторов.
Олег: Интересно, откуда эти проценты вообще берутся. Это какая-то изначальная неэффективность? Но тогда когда-нибудь этот запас неэффективностей закончится.
Сергей: Нет, это просто вопрос развития технологии. Я бросил заниматься компиляторами, когда стал работать над перформансом. Но когда-то я занимался, и самое большое открытие для меня было сделано в 2005 или 2006 году. Я узнал об этом вообще в 2008, потому что вовремя не прочитал статью.
Очень важная задача любой кодогенерации — это размещение регистров. Известно, что в общем виде эта задача — NP-полная. Решать ее очень сложно, и поэтому все компиляторы пытаются гонять какой-то приближенный алгоритм с той или иной степенью качества.
И вот выходит статья, где ребята доказывают, что в каких-то случаях, которые покрывают огромное количество компиляторов и огромное количество внутренних представлений с определенными ограничениями, для задачи размещения аллокации регистров существует точный полиномиальный алгоритм. Ура, поехали!
Это случилось в 2005 году, компиляторы, сделанные раньше, этого не знали.
Олег: Теперь можно сделать новый аллокатор для Java?
Сергей: Теперь, когда есть теоретическое решение, его можно переписать. Не вдавался в детали, но знаю, что ребята из Excelsior реализовали у себя алгоритм.
Олег: Мы недавно делали интервью с Клиффом Кликом, и он рассказывал, какой безумно сложный и безумной гениальный аллокатор он написал для Java. Не хотите написать ещё один?
Сергей: Нет.
Олег: Тот, что есть, в целом — нормальный?
Сергей: Нет, он не нормальный. Я со своей утилитарной точки зрения скажу, что смотрю в ассемблер и иногда вижу: «Ну да, вот здесь регистры легли плохо». Если я прибегу пинать наших компиляторщиков, и мы перепишем аллокатор, то что мы получим? Мы получим какой-то выигрыш, но вряд ли я его увижу кроме как на тех примерах, где я увидел неэффективное размещение регистров. Пока нет каких-то огромных провалов в этой области, всегда есть чем заняться и получить больше выигрыша.
Олег: Есть ли какие-то фронт работ в JDK, где вся подкапотная компиляторная или performance-магия вырывается на поверхность? Ты говоришь, что надо писать обычный нормальный код и всё будет хорошо, но это звучит подозрительно.
Сергей: Всё будет хорошо, пока тебе не нужно супер-пупер. Если тебе нужно совсем быстро, будь готов, что будешь переписывать всегда. На текущий момент, если взять абстрактное большое приложение, по большое счету, как оно написано — вообще не играет роли с точки зрения производительности.
С одной стороны, как только срабатывает garbage collector, он отжирает свои 10-20%, с другой стороны начинает всплывать архитектура приложения. Огромная проблема, которую я видел в куче приложений — они занимаются тем, что перекладывают данные. Мы взяли данные отсюда, переложили туда, там сделали какие-то преобразования. В целом, любая программа делает только это. Она перекладывает данные из одного места в другое каким-то образом. Но если у тебя в программе слишком много перекладываний, то компилятор ничем не поможет.
Олег: Ты можешь попробовать отследить какие-то простые вещи, вроде: вот этот кусок памяти меняется владельцами и перемещается между объектами вот в таком направлении.
Сергей: Нет, это вопрос дизайна. Я же не просто перекладываю, а перекладываю с модификациями, что-то с ними делаю. Самую большую выгоду, в реальных, массовых приложениях можно получить, если подумать: а нужно ли столько перекладываний. Вместо десяти сделать семь — это уже хорошо.
(Может показаться, что тут случайно задублировано одно и то же видео. На самом деле все проще, Хабр закэшировал не ту картинку с YouTube)
Собираем кошку из фарша
Олег: У нас только что была конференция Hydra про распределенные вычисления. И очень многие люди очень сильно заморачивались такими вещами, как модель стоимости, определение стоимости каждой операции — очень гранулярно, очень точно. Людям очень хочется выписать все инструкции, сложить стоимость каждой из них и посмотреть, сколько тактов займет твой код. Интересно, насколько этот подход работает в современной действительности. И если не работает, то что делать?
Сергей: Ну, на полпроцента может быть и работает. Есть мысли, как это попытаться объяснить. Я отослал бы к одной из своих старых презентаций, где показывал мясорубку. Входит мясо, а на выходе выходит фарш. Фарш выходит параллельно, а если кухня большая, то на ней работает несколько мясорубок, и все эти куски мяса там перекладываются распределенно. И как ты посчитаешь эти такты на секунду и всё прочее?
Олег: Задача: «как собрать кошку из фарша».
Сергей: Примерно так, да. Тебе нужен фарш, ты его на выходе получил. Но как он происходил, в каком порядке? Тут нет порядка. Современное железо — это динамическая система, начиная от процессора и заканчивая кластерами. Это большая динамика, которую пытаются настраивать, что-то оптимизируют на лету, и это невозможно просчитать.
Олег: А если пытаться её заставить работать линейно, то это приведет к потере производительности?
Сергей: Если её пытаться зафорсить линейно, то что нам это даст? Она будет предсказуемой, но это скажется на скорости. Сколько лет у нас нет прироста гигагерц в процессорах?
Олег: Ну, где-то лет пять?
Сергей: Я бы сказал, что они и десять лет не так сильно растут — так, колебания маркетингового уровня. А процессоры почему-то становятся быстрее. За счет чего? А за счет того, что вот в мясорубке есть дырки, откуда мясо лезет, и в следующем процессоре этих дырочек становится больше, мясо лезет быстрее. Ну, если ручку крутить достаточно быстро и мясо подавать в нужном количестве.
Олег: Количество голов тоже растет.
Сергей: Дело же не только в головах, производительность процессоров растет даже взятых на 1 ядро. Не так быстро, как во времена гонки за гигагерцы, но она растет. Появляются новые фишки в микроархитектурах, какие-то аппаратные решения, и что-то немного ускоряется.
Пять дней на тесты и другие задачи performance-инженера
Олег: Ты же наверняка компилируешь OpenJDK три раза за обед. Насколько реально с помощью железа ускорить сборку проекта? C++ наверняка не быстро собирается. Если купить самый топовый десктопный процессор — это поможет как-то?
Сергей: Я, честно сказать, не знаю. Никогда не заморачивался этим вопросом. На моем ноутбуке сборка OpenJDK занимает 15 минут. И у меня всегда есть 15 минут чем заняться параллельно.
Олег: Но тогда получается, что нужно очень хорошо все продумывать, и только уже готовое отправлять на пересборку. Ты не можешь два символа поменять и перезапустить все тесты.
Сергей: Так это неважно! Я не могу отправить на пересборку потому, что изменения чуть больше, чем два символа. У меня нет такой ситуации, что поменял два символа, собрал билд, запустил, посмотрел, через минуту снова поменял два символа и начал собирать. Такое возможно, но я лично с таким не сталкивался. У меня обычно происходит следующее: я делаю билд, потом сажусь, пишу скрипт (или не пишу, он уже у меня есть), и запускаю часа на три — в лучшем случае. Иногда он успевает отработать за ночь, но есть вещи, которые приходится ждать по пять дней.
Олег: Ой, а что это может быть такое?
Сергей: Например, минимальный, очень ограниченный прогон всей базы микробенчмарков Java.
Олег: Когда это нужно?
Сергей: Это, например, нужно для проекта Valhalla.
Олег: Вот ты поменял что-то в проекте, не важно, что — как ты поймешь, насколько далеко распространилось влияние твоих изменений? Сколько тестов тебе надо собрать? Ты же не можешь собрать все тесты со всеми профилями на всем железе в мире.
Сергей: А это надо знать, что поменялось. Локальный пример — у меня были работы в криптографическом алгоритме. Я взял эти алгоритмы, посмотрел на них, написал бенчмарки и увидел, что это можно разогнать. Предложил фикс, его подтвердили, всё. Мы крутимся в районе этих бенчмарок. Вот у нас 2-3 алгоритма, на которые повлиял тот кусок кода, вот различные длинные ключи входных данных, чтобы понять картинку немного в общих чертах, погоняли — замечательно, коротко и просто.
Или, например, наши замечательные inline-типы, которые рано или поздно дойдут до основной Java и о которых я буду рассказывать на Joker. Там совершенно другая ситуация. Там известно, что некоторые базовые Java-операции требуют изменения семантики, где появляются дополнительные runtime-проверки. И это уже большой red flag: runtime-проверки не могут стоит 0. А будет ли это вообще видно в реальной жизни? Маленькая проверка, параллельное исполнение операции на современных out of order-процессорах — чепуха.
Но ведь это уже гадание, это надо проверять. Например, нужно найти регрессии. А на чем искать регрессии? Вот у нас базовые два десятка больших бенчмарков и порядка двух тысяч маленьких. Давайте мы все это запустим и посмотрим. Запустим baseline, запустим модифицированную версию, а потом это всё будет работать 5-6 дней, мы ещё неделю будем сидеть и тупо сравнивать результат — есть там регрессия или нету. И хорошо, если ты увидел разницу, что где-то в 2 раза упала скорость — тогда срочно надо пинать кого-то и смотреть что произошло. Если же 3% выскочило на каком-то микробенчмарке, ты бьешься головой об стену. Эти 3% взялись откуда?
Олег: Правильно ли я понял, что вы определяете нужное место интуитивно?
Сергей: У нас нет никакой интуиции, у нас есть регрессионное тестирование. У нас есть регрессионное performance-тестирование, которое состоит из нескольких уровней. Понятно, что для частых коммитов и промоушенов у нас есть небольшое специально выбранное подмножество, для редких — большое.
У нас есть база данных со всеми трендами, куда заходишь и смотришь, что произошло. Рутинная работа performance-разработчика — когда ты приходишь с утра, на тебя сваливается какая-нибудь performance-регрессия в каком-то конкретном билде по сравнению с другим билдом, на определенных бенчмарках. Надо разобраться, в чем дело, и ты сидишь и разбираешься.
Понятно, что поиск регрессий автоматизирован, перепроверка регрессий автоматизирована, даже автоматизировано выявление, где случилась performance-проблема — в class libraries или в самом hotspot, что тоже очень ускоряет работу.
Как «войти» в performance
Олег: Допустим, какой-нибудь Вася Пупкин хочет стать performance-инженером, сейчас все хотят ими стать. Скажи, осмысленная ли формулировка задачи или нет, и как войти во всю эту тему?
Сергей: Это очень интересный вопрос. Что должен делать абстрактный Вася? Здесь вопрос в том, зачем вообще нужны performance-инженеры и кто может себе позволить иметь роскошь иметь performance-инженера.
Как правило, это вопрос вхождения в предметную область: те компании, которые жестко завязаны на большую базу клиентов, будь то Oracle, Twitter, Netflix или те, которым нужно выжимать какие-то копейки из железа. Потому что здесь ты выжал пару копеек из своего железа, а на всю твою систему это даст десятки или сотни тысяч денег выигрыша — это вполне нормальная ситуация. Как правило, в таких компаниях эти performance-инженеры тренируются сами по себе, под нужды компании.
Если это компания не какого-то конкретного проекта, а более обобщенная, то там держать performance-инженера просто расточительно, так ли часто он будет нужен? Нужно просто писать нормальный код, чтобы всё работало, и это гораздо важнее.
А с точки зрения того, как стать performance-инженером — нужно не стесняться копать. Есть такой классический тест: если человеку дать два куска кода и попросить провести какое-то performance review, то больше половины разработчиков завернут его в бенчмарки, измерят, выявят, что быстрее, и остановятся. Если на этом остановились — с performance у них плохо.
А если они говорят: «Это быстрее, потому что...», когда они прокопали дальше и нашли правильное объяснение, почему именно оно быстрее — это хорошо. То, что какой-то кусок кода работает быстрее — ну, кто его знает, может у тебя там серверный Брендан Грегг сидит и орёт на твои харддрайвы, вот оно и замедлилось.
Подготовка к Joker
Олег: Ты приезжаешь на Joker с докладом. Что за доклад?
Сергей: Про inline-типы, которые за это время переименовались.
Олег: Это то, что раньше называлось value-типами?
Сергей: Да, на самом деле там очень простое объяснение, почему они были переименованы. Параллельно с текущими разработками в коде идут модификации и спецификации. И люди поняли, что они выглядят страшно. Само слово value в текущей Java-спецификации встречается очень часто. У нас то value, сё value, передача параметров by value, что-то ещё by value, и вот мы вводим ещё один value type. Получается очень много масляного масла, когда эти сущности сложно разделять и очень неудобно читать.
Поэтому было решено их просто переименовать. Было голосование, опрос независимых людей, вплоть до того, что Remi Forax где-то у себя во Франции бегал и опрашивал студентов, чтобы выбрать название inline-типов. Правда, Роман Елизаров обиделся, потому что у них в Kotlin inline-типы были придуманы специально, чтобы не путать с value-типами Java, но так уж получилось.
Они дошли до вполне приличной стадии. У нас сейчас выходит третий мажорный релиз внутри value-типов, два года назад это был, что называется, mvt (minimum value types), год назад это был LW 1. Через недельку будет выпущен LW 2 — следующий релиз, когда поведение и ключевые аспекты этих типов уже вырисовались. Люди поигрались, посмотрели, как оно ведет себя в разных трудных ситуациях, и картинка начала вырисовываться. Поэтому я думаю, что полезно дать представление о том, что грядет, рассказать, какие performance-преимущества это даёт, как их достичь, что там может быть не очень хорошо, и показать людям, куда всё идет.
Олег: А я правильно понимаю, что эта штука влияет на весь дизайн кода внутри и её не забэкпортировать на семерку-восьмерку какую-нибудь?
Сергей: В принципе, какой-нибудь активный человек может и забэкпортировать. С другой стороны, меньше, чем на семерку, точно не сделать, потому что invokedynamic надо портировать.
Олег: То есть, это хороший стимул перейти, например, на новую версию, минимум восьмерку.
Сергей: Это жестко фиксированный дизайн, там меняется дизайн байткода, поэтому я не думаю, что это можно просто так бэкпортировать на предыдущие версии без малой крови. Теоретически — да, но я уверен, что никто не возьмется. Точно так же, кто портировал лямбды из восьмерки?
Олег: Для этого нужна, наверное, огромная экспертиза, и смысл этого непонятен.
Сергей: Вот да. С моей точки зрения, то, что грядут inline-типы и вслед за ними грядет реификация generic-ов со специализацией и прочими вкусностями, но для этого нужно иметь базовые inline-типы. Это будут очень крупные изменения на уровне языка, самые большие по сравнению с теми же лямбдами.
Олег: А текущее состояние про реификацию, оно где-то обновляется в публичном виде?
Сергей: Текущее обновление примерно следующее: как раз выпуск релиза inline-типов LW2 позволит заняться этим. Необходимо сначала сделать value-типы, а потом уже заниматься generic, потому что это завязанные и последовательные вещи.
Сергей Куксенко приедет с докладом «Нужны ли в Java инлайн-типы? Узкий взгляд инженера по производительности на проект Valhalla» на следующую конференцию Joker, которая пройдет в Санкт-Петербурге 25-26 октября 2019 года. Как всегда, билеты можно приобрести на официальном сайте.
Комментарии (6)
tmaxx
16.08.2019 10:12Могу ответить на вопрос про Chronicle и OpenHFT. Они написаны на чистой Java без JNI, как и большинство подобных вещей.
Причины этому я вижу две. Во-первых JNI имеет оверхед и в целом работает как этакая заноза для JIT, которую ни заинлайнить, ни переместить.
Во-вторых потребуется компилировать и как-то поставлять бинарник. Точнее 3 бинарника, хотя бы для самых популярных платформ (Solaris — все ещё не редкость в больших конторах). Точнее 6 бинарников (32-bit для совместимости и 64-bit для производительности).
Когда пишешь такие вещи «для внутреннего пользования», то хотя-бы знаешь на чем оно будет запускаться...
sved
16.08.2019 16:45Бинарник прямо в jar-ник пихается. Зачем что-то поставлять?
Смотрите к примеру xerial драйвер для sqllite.
Внутри скомпилированные бинарники для 12 платформ.tmaxx
16.08.2019 20:33Так обычно и делают, но бинарник ещё нужно подключить к JVM. “Runtime.loadLibrary” требует файл на диске, а не абстрактный Stream. Обычно копируют в /tmp, но не факт что есть permissions. И что SecurityManager вообще что-то позволит грузить.
Обычно таких сложностей не возникает, но это все ограничивает универсальность либы.
leventov
Спасибо за отличное интервью.
Мне кажется, это очень интересный вопрос.
Во-первых, культ производительности определенно есть не только в Джаве: посмотрите доклады на конференциях по Go, С++, JavaScript — там тоже очень много докладов про производительность, и зачастую это самые посещаемые доклады.
Думаю, культ во многом связан с эго: "если я написал код быстрее, чем коллега, значит я круче/умнее". Посмотрите на заголовки проектов на Гитхабе: многие имеют вид "Fast(est) [Thing] for [Language]". Вдобавок, производительность относительно легко сравнивать.
Было бы интересно видеть в индустрии больше проектов с заголовками типа:
severgun
Разве их устраивают не для того чтобы помериться «тем самым» с Java и Python?