«Типичная ошибка — бездумно бенчмаркать всё подряд»: интервью с Андреем Акиньшиным о бенчмаркинге
В прошлом году у Андрея Акиньшина (DreamWalker) вышла книга «Pro .NET Benchmarking»: подробнейший труд о бенчмаркинге, полезный как .NET-разработчикам, так и айтишникам других направлений.
Когда до её выхода оставалась пара месяцев, мы проводили конференцию DotNext 2019 Piter, где в онлайн-трансляции расспросили Андрея про книгу и вообще про бенчмаркинг. Казалось бы, с тех пор это интервью должно было устареть: там о книге говорят в будущем времени, а сейчас ей уже полгода. Но за эти полгода человечество не стало как-то иначе считать 99-й перцентиль — так что для всех, кому может пригодиться бенчмаркинг, в ответах Андрея по-прежнему много актуального и интересного.
А в апреле он выступит на DotNext с темой «Поговорим про перформанс-анализ» — то есть не про написание бенчмарков, а про анализ собранных ими значений. Прямо сейчас Андрей штудирует сотни статей по математической статистике, чтобы рассказать вам про те методы, которые лучше всего подходят для перфоманс-анализа в реальной жизни. В книге такому анализу тоже уделено внимание, а в интервью Андрей как раз объяснял его важность. Поэтому в ожидании нового доклада мы открыли для всех видеозапись интервью, а специально для Хабра сделали текстовую расшифровку: теперь его можно не только посмотреть, но и прочитать.
Главное о книге
— Приветствуем снова зрителей трансляции DotNext. В этот раз с нами Андрей Акиньшин.
— Всем привет!
— Сейчас главная новость, связанная с тобой — анонсирована книжка, которая выйдет к сентябрю…
— Если всё хорошо пойдёт, она выйдет в конце июня.
Здесь нужно понимать, как работают дедлайны. Есть самые крайние, которые нельзя нарушать ни в коем случае. На Amazon у книги сейчас указана дата выхода около 23 августа. И если эту дату зафейлить, пойдут всякие неустойки, Amazon будет недоволен. А если книжка выйдет раньше — ну и хорошо.
Поэтому я очень надеюсь, что, если нигде никаких проблем не будет, в июне уже можно будет почитать. А так, конец августа — самый крайний срок. Ты тоже в IT работаешь, так что понимаешь, как работают эти вещи.
— Вероятно, большая часть аудитории уже слышала о книге. Но для тех, кто не знает, начнём с рассказа о ней.
— Книжка называется «Pro .NET Benchmarking». Она из серии Apress — той же, в которой не так давно вышла книга Конрада Кокоса «Pro .NET Memory Management». А ещё там выходила книга Саши Гольдштейна «Pro .NET Performance» — про эту ты наверняка слышал, её на DotNext время от времени разыгрывают. И в этой же серии выходит моя книжка. Она про то, как делать бенчмаркинг от начала и до конца.
Я постарался покрыть самые разные аспекты, начиная от матстатистики, про неё есть отдельная глава. Причём не так, как нас в университете учили, у меня нет ни одного примера про «шары, которые кладут в коробки». Фокус на том, что может пригодиться именно во время бенчмаркинга: метрики, стандартное отклонение, стандартные ошибки, всякие confidence-интервалы и как их интерпретировать. То есть речь о следующем: если условный BenchmarkDotNet выдал тебе миллион разных цифр, что с ними сделать? Даются практические рекомендации, как интерпретировать эти данные и делать выводы.
Есть по главе, например, про CPU-bound бенчмарки и про memory-bound бенчмарки. Там очень много разных case studies с примерами того, как можно написать бенчмарк на 3–4 строчки и при этом выстрелить себе в ногу из-за каких-нибудь микроархитектурных эффектов современных интеловских процессоров.
А есть глава про перформанс-анализ и перформанс-тестирование. Бенчмаркинг — это хорошо как отдельный эксперимент, но многие люди хотят ставить бенчмарки на CI, гонять их всё время на каком-нибудь сервере (в идеальном случае одном и том же), собирать данные, чтобы, например, ловить перформансные деградации. Поэтому есть глава про то, как работать с такими данными и писать разные виды перформанс-тестов (их очень много разных). В чём, например, разница тестов на холодный старт, на горячий старт, как обрабатывать графики, как обрабатывать целые массивы данных.
На одном из прошлых DotNext я говорил про как раз перформанс-анализ, рассказывал про разные методы поиска перформанс-аномалий. Деградация — это не единственная проблема, которая может возникнуть. Например, бывают мультимодальные распределения, когда бенчмарк работает то одну секунду, то десять. В большом продукте (особенно многопоточном) такие случаи наверняка будут, и обычно они скрывают за собой проблему. Даже если речь не о перформанс-тестах, запущенных на соответствующих машинах, а обычные тесты чёрт знает как «дрожат» и дают большую дисперсию, то если все эти данные собрать и проанализировать, можно найти очень много тестов с такими проблемами.
В общем, в бенчмаркинге есть очень широкий спектр очень интересных задач, и я аккуратненько разложил их по полочкам. Но постарался сделать это максимально практично, чтобы это была не просто теория, а какие-то знания, которые можно взять и у себя на продукте в продакшне применить.
Тонкости бенчмаркинга
— Мне вспоминается где-то прочитанная фраза, что для любого результата бенчмарка в интернете можно найти неправильную интерпретацию этого результата. Насколько ты согласен с такой фразой?
— Абсолютно согласен. Есть много разговоров про валидные и невалидные бенчмарки, но если посмотреть с высоты птичьего полёта, то если ты что-то как-то померил, собрал хоть какие-то перформанс-метрики и вывел их в файл или консоль, то это бенчмарк и с определённой точки зрения он валиден: что-то измеряет и выводит какие-то цифры. Главный вопрос в том, как ты эти циферки интерпретируешь и что с ними будешь делать дальше.
Одна из первых ошибок людей, которые погружаются в тему бенчмаркинга — они собираются бенчмаркать всё подряд, не задавая себе вопрос «зачем». Ну вот мы побенчмаркали, померили — и что дальше? Очень важно определить, с какой целью мы делаем бенчмаркинг.
Например, если мы хотим сделать стабильный workload, с помощью которого сможем оценивать производительность определённых кейсов и обнаруживать перформанс-деградации — это один кейс. Другой кейс: у нас есть две библиотеки, которые делают одно и то же, а мы за перформанс и мы хотим выбрать из них самую быструю — как это сравнить?
С точки зрения интерпретаций любую, которая ведёт к правильному бизнес-решению, можно считать хорошей. Она не обязательно правильная, но если ты пришёл к успеху, то и хорошо.
А ещё есть такая вещь, по которой у меня в книжке даже даны специальные упражнения. Скажем, есть два алгоритма, и упражнение заключается в следующем: надо сначала сделать бенчмарк, который показывает, что первый алгоритм в 10 раз быстрее второго, а потом бенчмарк, который показывает противоположное. Можно играться с исходными данными, с окружением, поменять Mono на .NET Core, или запустить на Linux вместо Windows — есть миллион вариантов для кастомизации. И вывод такой: если ты задался целью показать, что одна программа работает быстрее другой, скорее всего, есть способ это сделать.
Поэтому, возвращаясь к твоему вопросу, очень сложно провести грань между валидными и невалидными бенчмарками и дать определение невалидного (то есть что там должно быть такого, чтобы мы признали, что он плохой). И то же самое с разграничением «правильной» и «неправильной» интерпретации: можно не до конца понять, что происходит в бенчмарке, не до конца объяснить все внутренние процессы (что не очень хорошо, лучше бы это сделать, но можно пропустить эту часть, если очень занят), но при этом понять в целом, как выглядит картина. И если удалось это сделать правильно (тут опять-таки вопрос, что такое «правильно») и прийти к правильному бизнес-решению, то ты молодец.
— Если просто взять и вдумчиво прочитать твою книгу, тогда начнёшь принимать «правильные» решения? Или есть много вещей за пределами скоупа книги, которые тоже влияют?
— Бенчмаркинг — это такая тема, которую, я считаю, можно освоить только на практике. Да, в книжке я даю очень много методологий, рекомендаций, описываю подводные камни. В бенчмаркинге есть очень много таких проблем, что если ты про них не знаешь, то в жизни никогда про них не догадаешься. Но если ты про них знаешь, это не даёт абсолютно никаких гарантий, что твои бенчмарки будут корректными. То есть это такой минимальный инструментарий, который помогает хоть как-то ориентироваться в области.
Нормальные бенчмарки и перформанс-тесты ты сможешь писать, только если систематически занимаешься этой областью. Нейронная сетка в голове тренируется на то, чтобы читать перформанс-репорты — когда смотришь на распределения, полученные в ходе перформансных замеров, смотришь на summary-таблицы, например, от BenchmarkDotNet (причём не только на колонку «Среднее», но и на стандартное отклонение), смотришь на стандартные ошибки, на дополнительные характеристики, на минимум, на максимум, на квантиль, на 99-й перцентиль.
Когда очень-очень-очень много смотришь на всё это — нарабатывается какой-то минимальный объём, позволяющий гораздо быстрее проводить перформанс-инвестигирование и видеть то, что люди без опыта (даже если они прочитали мою книжку и миллион блог-постов) не увидят просто из-за того, что у них нет опыта. Они не увидят каких-то проблем или не смогут мгновенно и корректно интерпретировать данные.
— На этом DotNext в интервью с Дмитрием Нестеруком (mezastel) мы говорили, что обычно айтишные книги быстро устаревают, но если он пишет про паттерны проектирования, то вот там всё не меняется каждый год. А что с бенчмаркингом: такая книга тоже может не устаревать очень долго, или ещё два года назад ты написал бы что-то иначе?
— Очень сложно дать односложный ответ. Есть некоторый базис, некоторая матчасть, которая не устраивает. Та же самая статистика: как 99-й перцентиль считался два года назад, он так до сих пор считается, и есть подозрение, что через два года ещё ничего не поменяется.
Кстати, заодно замечу: я считаю, что бенчмаркинг должен быть отдельной дисциплиной. Почему-то исторически никто не уделял должного систематического внимания перформанс-замерам. Ну что там? Взял, завёл таймер, выключил таймер, посмотрел, сколько прошло. А в книжке получилось, по предварительной оценке, больше 600 страниц, и меня всё спрашивают: «Что там можно было на 600 страниц написать?»
А я считаю, что это должно быть дисциплиной, отдельным направлением computer science. Причём это такое «language-agnostic» направление, где общая матчасть остаётся правильной и не изменится: это то, до чего в целом дошло человечество. Это применимо к любым рантаймам, языкам, экосистемам. Но это только одна часть ответа.
А другая часть уже завязана на особенности рантайма, на особенности .NET. Вот сейчас (об этом в книжке тоже много) у нас есть .NET Framework, есть .NET Core, есть Mono. Результаты перформансных замеров могут различаться на разных рантаймах или даже на двух соседних версиях одного рантайма. Если взять .NET Core 2.2 и скоро выходящий .NET Core 3.0 — некоторые workload«ы отличаются просто как день и ночь. Там делают такие крутые оптимизации, что простейшие сценарии разгоняют просто в 10 раз, в 50 раз.
Понятно, что если переехать на новую версию Core, вся программа не начнёт работать быстрее в 50 раз, но отдельные маленькие кусочки, которые чаще всего попадают в синтетические бенчмарки, вполне могут так разогнаться.
И то, что меняется, меняется в основном относительно всех этих версий, появляются новые оптимизации. Например, в .NET Core 3.0 появится tiered jitting. То есть рантайм сможет сначала сгенерировать быстренько одну простенькую (и не слишком эффективную) нативную реализацию для метода по коду. А затем, когда рантайм заметит, что ты этот метод вызываешь много-много раз, он потратит чуть больше времени в бэкграунде и перегенерирует более продуктивный код. Примерно то, что в Java в HotSpot есть уже много лет, в .NET мире появится в релизе, включенном по дефолту, в этом году во второй половине года (прим. ред.: напоминаем, интервью взято в мае 2019-го).
И для BenchmarkDotNet это вызов — нормально обрабатывать такие случаи. В Java-мире Алексей Шипилёв в своём JMH давно научился нормально это обрабатывать, а нам ещё предстоит. На эту тему тоже общаюсь с ребятами, которые пилят рантайм. То есть мне понадобятся от них специальные ручки, API, чтобы со всем корректно разобраться.
Вот такие вещи меняются. Скоро у нас вообще все рантаймы объединятся, будет один .NET 5. Я предполагаю, что его как-нибудь по-другому переназовут, что это промежуточное название. Может, это будет не 5, а 6, потому что версия .NET Core 5.0 у нас уже была.
— Ну, как мы знаем по Windows, для Microsoft не проблема пропустить число в версии.
— Да. Уже во времена DNX там были target framework«и с пятым .NET Core, сейчас »5.0» заиспользовано уже очень много где, есть много старых постов. Так что не знаю, они сейчас как бы собираются после третьей версии делать пятую, но я бы пропустил не только четвёрку, а пятёрку тоже, и сделал бы сразу шестую. А с учётом того, что они сейчас хотят сделать, по-моему, нечётные версии стабильными LTS, а чётные — не очень стабильными, можно было бы и сразу семёрку.
Ну, это уже их головная боль. Но важно, что нужно следить за развитием рантаймов, и вот именно эта .NET-specific часть устаревает — не то чтобы очень быстро устаревает, но потихонечку.
Я уже подумываю сделать второе издание книжки, где всё это проапдейтить. Интеловские процессоры тоже на месте не стоят, они развиваются, появляются новые оптимизации, которые тоже нужно по-хитрому обрабатывать. Skylake очень много неприятных сюрпризов преподнёс, в том же BenchmarkDotNet было проделано очень много работы, чтобы обойти его хитрые оптимизации и получить стабильные результаты.
Взаимодействие с BenchmarkDotNet и Rider
— Понятно, что работа над библиотекой BenchmarkDotNet дала тебе много опыта, поэтому логично, что книгу на тему бенчмаркинга написал именно ты. И тут возникает вопрос:, а книга как-то привязана к BenchmarkDotNet, или она «tool-agnostic»?
— Я постарался сделать её tool-agnostic. Про BenchmarkDotNet у меня есть одна маленькая секция, и ещё я его использую в качестве примеров в своих case studies: когда мне нужно показать какой-нибудь маленький микроархитектурный эффект, я говорю «вот напишем бенчмарк с использованием BenchmarkDotNet». Просто чтобы не городить в книжке миллион обвязок в каждом бенчмарке, нигде не писать отдельно логику прогрева. У нас уже есть готовое решение, которое делает всю бенчмарковую рутину за нас, а мы поговорим уже не про методологию (про неё в начале поговорили), а поговорим про эффекты на уровне CPU.
Вот два случая использования, а всё остальное я попытался сделать максимально абстрагированно от BenchmarkDotNet. Чтобы книжка была полезна не только .NET-разработчикам, но и, например, Java-разработчикам. Потому что все общие механики легко переносятся на любые другие платформы, то есть .NET и BenchmarkDotNet используются как инструмент для иллюстрации концепта.
— А в другую сторону влияние было? Что в процессе работы над книгой в итоге понял, что надо в BenchmarkDotNet вот так-то сделать?
— Да, я дописал много всяких мелких фишечек специально для того, чтобы они были в книжке. Например, клёвый детектинг мультимодальных распределений, про который я уже говорил.
По-хорошему, когда ты анализируешь результаты бенчмарка, ты всегда должен смотреть на распределение, открывать картинку, изучать, что там получилось. Но на практике никто так не делает. Потому что если я запускаю, условно, 50 бенчмарков на какую-то кодовую базу, а эту кодовую базу меняю 10 раз в день, и каждый раз перезапускаю полный сет, то смотреть 50 графиков даже я, конечно же, не буду, мне это делать лениво. И в этом, по большому счёту, нет смысла, это не задача человека, это задача тулинга.
В BenchmarkDotNet есть клёвый алгоритм, который автоматически определяет, что распределение мультимодальное, и предупреждает пользователя: «Чувак! Посмотри на график! Вот здесь всё плохо! Вот там в колонке зарепортилось среднее значение, вот не смотри на него! Оно ничему не соответствует, посмотри на график!»
И это печатается только в тех случаях, когда действительно важно, чтобы зря не отвлекать человека на графики. Там подход, основанный на так называемых m values от Брендана Грегга, это ведущий перформанс-инженер в Netflix.
Но его подхода мне не хватило, потому что он использует специальным образом построенные гистограммы на основе распределения. То есть на вход подаётся гистограмма, считается n value и по нему магически определяется, мультимодальное у нас распределение или нет. А как построить гистограммы, Брендан Грегг не написал! Пришлось изобрести какой-то свой велосипед, который на удивление хорошо заработал. Этот алгоритм приведён вкратце в книжке.
Таких историй довольно много было. Непосредственно написание книги у меня заняло два с половиной года. Я в целом пять лет собирал контент, а два с половиной года с того момента, как заключил договор с издательством. Вот за эти два с половиной года благодаря книжке библиотека во многом прокачалась, там много чего появилось.
— Сложно представить себе, но помимо книги и BenchmarkDotNet, в твоей жизни есть ещё и работа над Rider — и там ты наверняка тоже бенчмаркаешь. Можешь рассказать об этом? У тебя в твиттере были фотографии макбука в морозилке и рядом с обогревателем, с проверкой, как это влияет на производительность — это было по работе, или для книжки, или то и другое сразу?
— Скорее всё вместе. В Rider мы используем BenchmarkDotNet для отдельных перформанс-инвестигирований. То есть когда нужно разобраться, как лучше написать код в каком-нибудь performance critical-куске, или надо изучить, как у нас отличается поведение куска кода под Mono на Linux и под .NET Framework на Windows. Мы берём BenchmarkDotNet, проектируем эксперимент, собираем результаты, делаем выводы, принимаем бизнес-решения, как написать код, чтобы он везде работал быстро. И дальше этот бенчмарк выкидывается.
То есть у нас нет на систематической основе бенчмарков на BenchmarkDotNet, которые запускались бы на CI. Но вместо этого у нас есть много других направлений по перформанс-работе. Например, внутренний инструмент, который собирает цифры со всех тестов и ищет в них разные перформанс-аномалии, те же мультимодальные распределения, тесты с каким-нибудь большим стандартным отклонением, и собирает это всё в один dashboard.
Другой подход, над которым мы уже очень долго работаем, но так и не сделали — это надёжные перформанс-тесты. То есть мы хотим сделать подход, в котором никак невозможно замёржить в master-ветку перформанс-деградацию.
И классические бенчмарки не очень подходят, потому что они очень ресурсоёмкие. Нужно сделать очень много итераций, чтобы получить нормальную статистику и как-то с ней работать. А когда у тебя есть сотни или тысячи перформанс-тестов, если ты каждый тест будешь прогонять 30 раз, как полагается, и это на каждый бранч каждого человека — никакого железа не хватит.
Поэтому, с одной стороны, хочется сделать как можно меньше итераций (в идеале одну, но по одной очень сложно сказать, есть ли у тебя деградация). Самое плохое, что может случиться — это false positive, когда ты ничего плохого не сделал, но система говорит о перформанс-деградациях и не даёт вмёржить бранч в master. Если так будет происходить, меня закидают камнями: и никто этой системой пользоваться не будет.
Поэтому, условно, если после одной итерации есть подозрение на перфдеградацию, но нет уверенности на 100%, стоит сделать вторую итерацию. После второй ты можешь принять решение, что у нас всё нормально, просто случайно что-то произошло. Можешь сказать, что теперь мы точно уверены в перформанс-деградации и запретить мёрж. А можешь сказать: «Нет, двух итераций всё ещё недостаточно, нам нужно идти на третью». Ну и так далее.
И на маленьком количестве итераций (одна-две-три) стандартные тесты вообще никак не работают. Вот мой любимый тест Манна—Уитни начинает нормально работать, когда у тебя есть хотя бы пять итераций. Но до пятой мы доходим, только когда всё совсем плохо. Соответственно, нужно разработать такой сет эвристик, который, никогда не будет давать false positive, но при этом будет обнаруживать деградации, когда они есть, с минимально возможным количеством итераций. И вот это довольно сложная задача на смесь программистской инженерии и математических формул. Мы ещё не добили, но идём к этому.
А по поводу макбука в холодильнике — это тоже всё по работе. Сейчас один из минипроектов, которым я довольно много занимаюсь — это исследование моделей термального троттлинга. Ситуация следующая: когда CPU-bound бенчмарк очень сильно грузит железо, растёт температура CPU, и когда она достигает определённого порогового значения, интеловский процессор или операционная система говорит: «Ай-яй-яй! Мы перегреваемся!» — и на какой-то промежуток времени снижает частоту. И дальше получаются, например, 2–3 итерации, на которых якобы видна перформанс-деградация. И мы такие: «Ой-ой-ой-ой-ой! Всё плохо! Мы не будем мержить этот бранч». А на самом деле у нас просто перформанс-агент перегрелся.
Есть разные способы с этим бороться. У нас есть своя серверная со своими стоечками, мы пытаемся там обеспечить достаточное охлаждение, чтобы этого термального троттлинга не возникало. Но это тоже не всегда удаётся. То есть совсем заморозить агенты не можем, им от этого будет не очень, а бороться как-то надо.
Другой вариант — это, например, отключение турбобуста, чтобы процессор никогда не выходил за базовую частоту. Это, соответственно, снижает вероятность перегрева, процессор уже не так греется. А во-вторых, у нас получается более стабильная частота (в турбобусте она частенько довольно сильно дрожит, а с отключенным турбобустом на базовой частоте прямо чётенько идёт, и у тебя результат получается намного более стабильный).
И модели термального троттлинга очень разные: во-первых, многое зависит от процессора и от конфигурации всего железа, во-вторых, от операционной системы. Например, возьмём Mac: у нас довольно много перфтестов на Mac, потому что пользователей на них много и они не хотят, чтобы Rider тормозил. И там очень агрессивная модель термального троттлинга.
На новых интеловских процессорах, которые недавно анонсировали, есть ещё более сложные приколы. Если у тебя температура опускается ниже определённого порогового значения, вроде 50 градусов, то частота может скакнуть даже выше, чем максимальная частота на обычном турбобусте. То есть они делают что-то типа динамического оверклокинга «на чуть-чуть» в условиях низких температур. Тоже эффект. В наших агентах ещё условно старенькие, процессоры, ещё не проапгрейдились, но гики, которые любят покупать себе всё самое новое, могут на это наступить.
Будущее
— Придётся тебя прервать, потому что время заканчивается. Но для тех, кто заинтригован: ты собираешься по этому материалу потом написать блог-пост?
— Да, я пока собираю материал, там очень всё интересно, очень сложная модель термального троттлинга. Есть ещё, например, Power Throttling на Windows, который позволяет экономить заряд батарейки и ещё много всего. Пока я собираю данные, а потом это всё объединю либо в блог-пост, либо даже в научную статью, либо это попадёт во второе издание книжки.
— Хорошо. Спасибо за ответы!
Если вам было интересно и захотелось как следует приобщиться к знаниям Андрея, у вас есть два варианта. Один — это, конечно, прочитать книгу.А второй вариант — посетить нашу конференцию DotNext 2020 Piter и послушать кейноут Андрея о перформанс-анализе. Конечно, в доклад помещается меньше материала, чем в книгу, но у этого формата есть свои преимущества. Во-первых, в докладе будет очень много новой информации, которая в книжку не попала. Во-вторых, можно будет лично позадавать Андрею вопросы в дискуссионной зоне. Ну и, в-третьих, DotNext — это возможность увидеть сразу и Андрея, и многих других спикеров, включая Дядюшку Боба и Джона Скита! Вся актуальная информация — на сайте конференции.