Говорим про системное программирование и дизайн микропроцессоров простым языком
Системное программирование и разработка процессоров — область достаточно узкая, из-за чего её часто воспринимают как что-то непонятное и недоступное. Хотим поделиться новым подкастом «Битовые маски», который планирует исправить это впечатление. В каждом выпуске будем общаться с инженерами, причастными к созданию продуктов, которыми многие программисты пользуются ежедневно, и разбирать с ними интересные нюансы, мифы и задачи отрасли.
Гостем первого эпизода стал Дмитрий Петров, писавший компилятор для Kotlin. Для тех, кто не любит слушать — расшифровка ряда интересных фрагментов ниже. Мы очень хотим фидбека: не стесняйтесь писать в комментарии или личные сообщения.
Мы уже записываем новые эпизоды — подписывайтесь на ютуб-плейлист, чтобы не пропустить. Вскоре «Битовые маски» появятся и на других платформах.
«Наверно, я сейчас ближе к бэкенду. Но это вообще не то, о чем ты думаешь»
Елена Лепилкина, соавтор подкаста: Меня всегда веселит, когда человек что-то знает о программировании и спрашивает: «Чем ты занимаешься? Бэкенд или фронтенд?».
Антон Афанасьев, соавтор подкаста: Да, думаю, для многих бэкенд и фронтенд — это термины из области веб-разработки. В нашем мире обычно компилятор делят на три крупные части: фронтенд, миддленд и бэкенд. Насколько легко системному программисту переходить из фронтенда в бэкенд или миддленд? Есть какие-то границы?
Дмитрий Петров: По-разному, и обычно зависит от того, насколько большая команда занимается этим компилятором.
20+ лет создает системное ПО. Разрабатывал С/С++ компилятор в МЦСТ, Kotlin-компилятор в JetBrains, работал в Intel Labs. Сейчас разрабатывает компилятор C/C++ для RISC-V архитектуры.
В каждой из этих областей решают свои задачи. Пример интересных задач во фронтенде — вывод типов или разрешение перегрузок, которые связаны с выводом типов. В миддленде это различные высокоуровневые оптимизации, например, преобразование над циклами. В бэкенде — низкоуровневые, скажем, статическое планирование выполнения инструкций.
У них одни и те же общие принципы, но работа разная чисто технически. Бэкендеры обычно гораздо сильнее зарываются в тонкие особенности целевой платформы, хотя в миддленде это тоже важно.
«Очень мало кто обращает внимание на это, когда пишет код приложения» — проблема параллелизма на уровне инструкций
Антон Афанасьев: Что такое instruction scheduling?
Дмитрий Петров: Это попытка на уровне компиляции так расположить инструкции, чтобы извлечь максимум из вычислительных ресурсов процессора. В том числе этим я как раз занимаюсь.
Елена Лепилкина: Обычно здесь есть ограничения по памяти. Можно ли всё переставить эффективно?
Дмитрий Петров: Конечно нет.
Довольно часто у компилятора бывают связаны руки, потому что очень мало кто обращает внимание на такие подробности, когда пишет код приложения.
Например, компилятор не всегда может сделать вывод, что два указателя указывают на непересекающиеся области памяти. В таком случае он всегда сделает оптимизацию из консервативных предположений о том, что эти операции читают или пишут что-то, что может пересекаться. Эти операции зависимы, и их никак нельзя переупорядочивать, даже если компилятор счёл бы это более выгодным, потому что это просто нарушит семантику исполнения программы.
Елена Лепилкина: Чтобы оценить стоимость этих инструкций и расписание нужна информация. Как она собирается? Есть ли у разработчиков компиляторов лучшие практики, чтобы построить cost-модель?
Дмитрий Петров: Если мы имеем доступ к документации по целевому процессору, нужно посмотреть в неё. Но и тут доверяй, но проверяй: если процессор находится в разработке, документация может не поспевать за его реальным состоянием.
Тогда можно использовать инструменты, которые позволяют экспериментальным путем оценить, сколько времени занимает та или иная инструкция. Такой инструмент, в частности есть во фреймворке LLVM — llvm-exegesis.
Если у нас есть доступ к исходному коду процессора, то можно собрать его симулятор из Verilog и посмотреть трассы исполнения. Обладая таким «рентгеновским зрением» и полным знанием о том, что происходит, можно взять и сказать: «Пять тактов».
Наконец, иногда мы просто можем задать вопрос разработчикам железа.
Про бизнес-задачи, бенчмарки и откуда они берутся
Дмитрий Петров: Если мы имеем доступ целевым приложениям, которые будут в основном бежать на нашей архитектуре, то для нас главное — не результаты бенчмарков, а то, что будет, например, исполняться в системе хранения данных. Скажем, если мы делаем графический ускоритель, для нас важно, чтобы быстро обсчитывались треугольники. И совершенно не важно, сколько будет занимать coremark на нашей графической карте.
Есть бенчмарки общего назначения для процессоров общего назначения. А есть конкретные жизненные и бизнес-задачи, под которые делаются специальные процессоры или оптимизируются готовые процессоры общего назначения.
И в итоге важно, насколько хорошо реальный клиентский код, скомпилированный этим компилятором, бежит на этом процессоре. Потому что за что клиент платит деньги? За производительность.
Антон Афанасьев: Даже если это 1%.
Дмитрий Петров: Иногда да.1% — это одна конкретная компиляторная оптимизация. Упорным накоплением 1–5% процентов за раз и происходит оптимизация компиляторов. Ну, а если набрать 20 оптимизаций, то вот уже и 20%.
Антон Афанасьев: Я бы ещё умножал этот 1% не только на количество оптимизаций, но и на число людей, которые будут пользоваться этим процентом. У компиляторов тысячи пользователей —, а значит, мы можем сэкономить очень много человеко-часов.
Про баги в JVM — байки из времен работы с Kotlin-компилятором и JIT
Антон Афанасьев: Вспомни пример бага, связанного с JVM, с JIT в нём, с тем, как это идет от Kotlin на JVM, и оттуда — на целевую архитектуру?
Дмитрий Петров: Типичный пример — как мы гонялись за JVM-ными оптимизациями конкатенации строк. В JVM от релиза к релизу в HotSpot менялись эвристики того, как JIT оптимизирует конкатенацию строк. Видимо, они подгонялись под всякие бенчмарки, типа SPECjbb.
Антон Афанасьев: То есть JVM распознавал конкатенацию?
Дмитрий Петров: Да. JIT разными своими эвристиками распознавал некоторые паттерны. Он всё знал про конкатенацию, что это метод StringBuilder.append. Но разные паттерны конкатенации строк он компилировал немного с разной производительностью. И мы очень старались сгенерировать код, максимально похожий на то, что делает в этом месте javac, чтобы JIT было полегче.
Это очень специфическая и не главная деталь работы. Но хороший пример того, что есть целевая платформа, у которой очень странные особенности, и за ними нужно все время следить.
Антон Афанасьев: И опять же, документации на нее нет.
Дмитрий Петров: Да. Лучшая документации — это исходный код. И логи JIT.
Если вам понравилось
Предлагаем послушать выпуск полностью: мы также поговорили про инструменты разработки, советы начинающим программистам, разных игроков и продукты в отрасли и как она поменялась за 20+ лет. И подписаться на нас здесь. Мы планруем выпускать по эпизоду в месяц. Выход на других платформах скоро.
Также оставляйте свои комментарии и пожелания по темам под постом — нам важен фидбек!
Об авторах и идее подкаста
Подкаст придумали и ведут Елена Лепилкина и Антон Афанасьев.
Ранее участвовала в разработке С/С++ компилятора на базе LLVM для ARC архитектуры в Synopsys и Kotlin/Native компилятора в JetBrains.
До этого занимался компиляторами в AMD, Intel, Sony.
Название подкаста отсылает к идее ребята в хорошем смысле «срывать маски» с крутых коллег по отрасли — представлять их более широкой аудитории, и через их опыт показать историю развития и крупных игроков в этой сфере, а также различные варианты развития карьеры для системных программистов.