Ускорение компиляции на C++ 🏃
Очень боянистая тема "ускорение компиляции" - это специальный вид спорта, в котором принимали участие многие разработчики на C++, которым не нравится медленная сборка. Я перечислю основные направления этого спорта.😃
Основным источником проблем является "N*M problem", почти все подходы стараются избежать именно его. Это значит, что если в проекте есть N хидеров и M не-хидеров, то суммарное количество распарсенных файлов в процессе компиляции будет стремиться к N*M, потому что каждый не-хидер компилируется отдельно и транзитивно подключает почти все хидеры, если за этим не следить.
Суммарный размер всех подключенных хидеров может достигать сотен тысяч строк.
Это актуально не только при билде с нуля. Если изменить какой-нибудь важный хидер, который включается почти везде, то это почти равносильно билду с нуля. В запущенных случаях изменения в почти всех хидерах триггерят пересборку с нуля.
В идеале надо стремиться к N+M, как это сделано во многих языках.
💪💪 PImpl
PImpl это супер боянистая идиома (почитать можно тут), чья цель изначально скрыть API класса, но от ее использования есть хороший побочный эффект - API может не подключать хидеры нужного класса, если там используется только указатель на класс!
Минусы:
⏰Объект нужного класса придется хранить в куче. Но есть сильное колдунство Fast PImpl (https://t.me/cxx95/27), которое впрочем имеет свои минусы.
⏰Сложно поддерживать, нужна сила воли.
💪💪 Jumbo build (он же Unity build, он же Single Translation Unit)
Можно почитать тут. Unity-билд это подход, когда все файлы модуля строятся за один присест. Это значит что если у нас есть файлы
"aaa.cpp", "bbb.cpp", ..., "zzz.cpp", то создается (желательно автоматически) файл который их всех подключает:/* all.cpp */И билдится только файл
#include "aaa.cpp"
#include "bbb.cpp"
// ...
#include "zzz.cpp"
"all.cpp". Смысл в том, что если 90%+ времени компиляции занимает не сам файл, а хидеры которые их подключают, то такой подход почти линейно ускорит компиляцию.Минусы:
⏰Будут конфликтовать имена статических методов и переменных
⏰Вообще это какая-то дичь и лечит симптомы, а не причины
💪💪 Precompiled headers
Можно почитать тут. Если у нас есть какие-то очень редко меняющиеся хидеры, то их можно "прекомпилировать".
Компилятор Clang делает это так - читает хидеры (например
test.h), анализирует и сохраняет в типа уже "распарсенном" формате (например в test.h.pch).Смысл в том, что если в коде где-то есть инклюд хидера
test.h, то компилятор не будет его парсить с нуля, а прочитает test.h.pch, что будет типа быстрее, так как там уже готовое синтаксическое дерево файла.Минусы:
⏰Как я ни пробовал, у меня это нифига не работает значительно быстрее. В интернете много споров с этим подходом.
💪💪 Include What You Use
Это тулза для удаления лишних инклюдов. Есть хорошая документация. Иногда бывают баги связанные с тем что математически доказать ненужность инклюда нельзя - они все неявно влияют друг на друга.
💪💪 Быстрая виртуалка
Ускорять компиляцию можно не только руками. Здесь я описал, как можно использовать виртуалки с большими ресурсами для разработки - https://t.me/cxx95/60
💪💪 Распределенная сборка
В крупных компаниях с крупными проектами используется распределенная сборка - что-нибудь готовое (distcc) или даже свое (недавняя статья от VK).
Эта большая тема, в статье круто описано как выглядит распределенная сборка 😃 Кроме того, чтобы не собирать всякие объектники по многу раз, можно использовать кэши - если твой коллега уже запускал сборку и ждал результатов, то тебе ждать не придется.
💪💪 Новый модный линкер mold
С линкерами в принципе проблем нет, но если у вас бинарь с Debug-символами в несколько гигабайт, то он может линковать вечность, по 2-3-10 минут.
Сейчас разрабатывается новый линкер mold, который типа быстрее других линкеров, но про него нет внятной документации и кое-кто жалуется на нестабильность и поломанные бинари.