Тяжелый H[header]

от автора

Всегда хотел написать о чем-нибудь легком и воздушном, как пишет например @antoshkka про userver или о том, как легко и непринужденно обернуть какую-нибудь хрень алгоритм в десяток шаблонов, полить это все std::optional и попивая кофе ждать, когда компилятор соизволит это всё пережевать. Но судьба (а не тимлид, нет, как вы могли такое подумать) постоянно подкидывает задачки, где суровые объятия отладчика не отпускают мечтательную душу программера до поздней ночи, да вечная борьба с компилятором рушит все попытки обернуть результат хрени алгоритма в другой десяток шаблонов. На этот раз судьба ясным июньским утром подкинула забавную задачу — время полной сборки бандла подбиралось к двум часам, да собирать бандлы нынче удовольствие не из быстрых, но посмотрев статистику стало понятно, что ~55% процентов времени тратится на сборку ресурсов: текстур, моделей, локализацию, и тд. Там есть что чинить, но это царство билд-инженеров. Еще 30% или сорок минут тратится на тесты, теперь все что мы насобирали и переконвертили надо проверить, загрузить, пострелять, побегать, монстров поубивать, BT-шки погонять, с этим пусть QA разбираются. А вот оставшиеся 15% или около 15 минут мы занимались «настоящей работой», собирали сердце проекта — бинарь. Да норм, у нас всегда так, даже на пустом проекте UE — сказали наши мобильщики и ушли пить кофе на терассу . Но мы же не мобильщики, мы серьезные AAA ребята, у нас свой движок и кастомный пайплайн на билдферме. И потом 15 минут это всё равно много, даже если у тебя 27к файлов в проекте, айда смотреть куда время потратили.


Хитрый тимлид

Но сначала с этими вопросами я пошел к архитекту и техлиду. 15 минут — ну реально много. Техлид ничем не помог ибо майлстоун и вообще фичи пилить нада, а от архитекта были только вопросы вроде «Репа на SSD?», «А на рам диске смотрели?» «А что насчет [вставьте здесь что-то свое]?». И да в принципе он прав, и нет, я не тестировал ничего из этого, потому что было лень возиться с новой погремушкой, и потому что мой домашний питомец из 2.5к файлов собирается за 35+- секунд с нуля. Экстраполируя эти данные —
27к должны собираться примерно 35 * 10 = 6 минут. А вообще старожилы на проекте говорили, что несколько лет назад проект собирался за 3 минуты, а файлов там было не так, чтобы сильно меньше.

15 минут 32 секунды (Чиним паразита)

Наверное можно попробовать обвинить «жЫрные» шаблоны, компиляторы, медленные компьютеры или Луну не в той фазе. Но самый простой способ выяснить, в чем заключается основная проблема — это не сразу запускать любимый (procmon, vcperf, и clang-trace, выбери своё) или проводить какие-то безумные научные тесты, как я. Для начала достаточно просто посмотреть на процессы с наибольшей нагрузкой и их время работы на ЦП.

Итак прежде чем открыть vcperf я решил заглянуть в task manager, и вижу там очень странную картину, студия отжирает себе 17% времени cpu, а еще браузер, антивирус и explorer. Что-то тут не так, с какого перепугу там вообще светятся все эти товарищи?

Не смотрите, что старенькая студия — проект прогоняется на нескольких компиляторах.

Chrome. Ладно браузер, это известный пожиратель всего, до чего сможет дотянуться, но тут ничего не поделаешь, либо документация и котики, либо время компиляции, так и быть пусть живёт. Обратите внимание, это пока всё очень не научно, я просто исхожу из того, что чем меньше других процессов, тем больше времени достанется компилятору.

Проводник?! Что ты тут вообще делаешь проводник? Ну во первых это не только инструмент для управления файлами и каталогами, это ещё и оболочка. Если его кикнуть, то исчезнут рабочий стол, пуск и, по сути, всё остальное. Тут только ругать криворуких индусов, которые его так написали. Во-вторых наш билд поднимал отдельное консольное окно, куда кидал всякое разное во время сборки из пайпа студии и куда могли цепляться другие тулы для анализа, например сборщик ассертов или других внутренних предупреждений о ходе сборки. И судя по ProcessExplorer консоль тоже управляется через explorer. Но это не причина, просто выключив эту консоль получили прирост что-то около 15с, да 15с это много, но это явно не причина.

Visual Studio?! Родная, ты же должна была себе забрать 100% ресурсов, и компилить-компилить-компилить.

Ииииииии... Я скомпилиииииииииииила! > Build succeeded.  >    0 Error(s) > Time Elapsed 00:15:32.530

Antimalware Service Executable, антивирус, и как любой антивирус, он будет серьезно смотреть что мы делаем — а мы там плюшками балуемся, перебираем 27к файлов, и создаем еще порядка 60к файлов объектников и временных tmp файлов. Похоже доблестные DevOps забыли внести репу в список исключений. Поправим это упущенеие. Результат: получили примерно 10% ускорение общего времени компиляции. Было 15:32с, стало 14:12с. Приятно, но это явно не серебрянная пуля, хотя и маленькая победа.

> Build succeeded. >    0 Error(s) > Time Elapsed 00:14:12.240

Так, вроде бы ничего больше танцору не мешает, но время компиляции все равно 14 минут. Открываем vcperf и смотрим куда уходит время, а уходит оно на компиляцию и парсинг хедеров. Так появилась идея посмотреть, сколько конкретно времени мы тратим на обработку каждого файла. Полем проверки для последующих изменений стал рабочий проект, целью было сделать время сборки как можно меньше. Возможно, какое-то время тратится на запуск компилятора, но не будем углубляться в этот момент. Так как у нас довольно большая кодовая база, то найденные зависимости помогли неплохо снизить время сборки.

! Извините, пути пришлось закрасить. CM не пропустил 🙁

В результате этой работы получился набор тестов и как результат — сводная таблица времени сборки заголовочных файлов. Я стараюсь избегать обобщения результатов, потому что эти тесты проводились на одной машине, с одной конкретной целью. Поэтому выложу сырые данные для сравнения на нескольких компиляторах MS и Clang (Sony PS5). Это позволяет видеть зависимости в результатах, и надеюсь будет, как мне кажется, интересно большинству хабрачитателей.

Результаты по компиляторам

Header

VS17

VS19

clang (16.0)

algorithm

0.169

0.191

0.316

array

0.184

0.253

0.106

atomic

0.326

0.405

0.659

bitset

0.329

0.415

0.527

cassert

0.01

0.009

0.017

chrono

0.144

0.195

0.306

Другие хедеры
Header      VS17    VS19    clang climits 0.010.0090.018 cmath    0.0360.0450.084 condition_variable0.2860.3720.51 cstdint    0.0110.0090.018 cstdio    0.1430.1430.173 deque    0.1830.2160.439 filesystem0.3830.5160.289 forward_list 0.1830.2140.338 fstream    1.2711.3311.476 functional  0.2870.2220.561 future    1.0591.3172.474 initializer_list0.0110.0130.022 ios        0.2590.3180.456 iosfwd    0.080.0940.172 iostream2.2642.3253.464 istream    1.2641.3241.463 iterator0.2650.3270.468 list    0.1830.2140.338 map        0.1940.2310.863 memory    0.1730.2    0.324 mutex    0.280.3640.598 ostream    1.2611.3210.459 queue    0.1980.2350.374 random    0.3050.3940.553 regex    2.3921.5051.634 set        0.1850.2170.341 sstream    0.3290.4160.528 stack    0.1860.2160.341 string    0.3270.4130.523 thread    0.2270.2890.448 tuple    0.1230.1630.263 type_traits0.0430.060.096 typeinfo0.0510.0680.107 unordered_map 0.2040.4450.184 utility    0.0980.1270.212 vector    0.2850.2170.244 windows.h4.4234.5177.038

Вы наверное заметили, что большинство тяжелых для компиляции хедеров (за исключением windows.h и iostream), а они реально тяжелые если время на их обработку больше 100мс, это шаблоны.

Header    VS17VS19clang  algorithm0.1690.1910.316 array    0.1840.2530.106 atomic    0.3260.4050.659 bitset    0.3290.4150.527 iterator0.2650.3270.468 vector    0.2850.2170.244

Шаблоны — одна из самых любимых мной возможностей C++, позволяющая мне писать так как я хочу, а не так, как этого требует стандарт. Шутка! Но, блин, почему они настолько медленные? Что там в array такого, что оно компилится 200мс, там же просто обертка над массивом. Заголовочный файл с шаблонами может быть включен только один раз, но реализация компилируется для каждой комбинации аргументов для каждого юнита компиляции. И дорогие шаблоны могут значительно увеличить время компиляции, что мы собственно и получили у себя. Возможно, там какие-то проблемы с инстанцированием, память там стеке разместить, проверки разные. Или вот вектор, тоже вроде ничего сложного должно быть, но тоже 200 с лишним мс на всех компиляторах.

Я посмотрел на godbolt (/d1reportTime) (https://godbolt.org/z/WEqx7zW8r), clang такого к сожалению не умеет, сколько занимает время компиляции каждой функции vector.

std::vector<int,class std::allocator<int> >::_Construct_n: 0.000566s std::vector<int,class std::allocator<int> >::vector: 0.000564s         std::vector<int,class std::allocator<int> >::_Tidy: 0.000341s         std::vector<int,class std::allocator<int> >::_Buy_raw: 0.000262s std::_Compressed_pair<class std::allocator<int>,class std::_Vector_val<struct std::_Simple_types<int> >,1>::_Compressed_pair: 0.000248s std::vector<int,class std::allocator<int> >::max_size: 0.000244s         std::_Vector_const_iterator<`template-type-parameter-1'>: 0.000295s std::_Vector_iterator<`template-type-parameter-1'>: 0.000184s         std::_Vector_val<`template-type-parameter-1'>: 0.000096s std::vector<`template-type-parameter-1',`template-type-parameter-2'>: 0.001980s         std::vector<int,class std::allocator<int> >: 0.002384s std::allocator_traits<class std::allocator<int> >: 0.000518s std::_Default_allocator_traits<class std::allocator<int> >: 0.000451s std::allocator<int>: 0.000265s std::_Compressed_pair<class std::allocator<int>,class std::_Vector_val<struct std::_Simple_types<int> >,1>: 0.000274s std::_Vector_val<struct std::_Simple_types<int> >: 0.000145s std::_Simple_types<int>: 0.000035s                     std::vector<int,class std::allocator<int> >: 0.002384s std::vector<`template-type-parameter-1',`template-type-parameter-2'>: 0.001980s         std::vector<bool,`template-type-parameter-1'>: 0.000774s         std::_Vector_const_iterator<`template-type-parameter-1'>: 0.000295s         std::_Vector_iterator<`template-type-parameter-1'>: 0.000184s         std::_Vector_val<`template-type-parameter-1'>: 0.000096s         std::vector<int,class std::allocator<int> >::operator []: 0.000081s std::vector<int,class std::allocator<int> >::~vector: 0.000029s std::vector<int,class std::allocator<int> >::vector: 0.000387s std::allocator<int>::allocator: 0.000017s std::vector<int,class std::allocator<int> >::_Tidy: 0.000232s std::vector<int,class std::allocator<int> >::_Getal: 0.000028s         std::_Compressed_pair<class std::allocator<int>,class std::_Vector_val<struct std::_Simple_types<int> >,1>::_Compressed_pair: 0.000172s std::vector<int,class std::allocator<int> >::_Construct_n: 0.000404s         std::_Vector_val<struct std::_Simple_types<int> >::_Vector_val: 0.000045s std::vector<int,class std::allocator<int> >::_Buy_nonzero: 0.000047s std::vector<int,class std::allocator<int> >::_Xlength: 0.000024s std::vector<int,class std::allocator<int> >::_Buy_raw: 0.000177s std::vector<int,class std::allocator<int> >::max_size: 0.000169s std::vector<int,class std::allocator<int> >::_Getal: 0.000029s         std::vector<int,class std::allocator<int> >::_Construct_n: 0.000404s std::vector<int,class std::allocator<int> >::vector: 0.000387s

В сумме набралось: 0.326898s
Z:\compilers\msvc\14.41.33923-14.41.33923.0\include\vector: 0.326898s

Это крошечные времена. Они даже особо не важны, правда? Кому важен процесс компиляцииstd::vector::<>max_size -> 0.000169s если он занимает две десятых миллисекунды? Это настолько несущественно, чтобы волноваться…

14 минут 12 секунд (Мучаем шаблоны)

> Build succeeded. >    0 Error(s) > Time Elapsed 00:14:12.240

Волноваться стоит если у вас 27к файлов в проекте. 27k * 0.326 = 8 880 секунд — почти три часа 🙂 Хорошо, что у нас вектор не в каждом файле используется. 14 минут, конечно не три часа, но тоже много, давайте смотреть как уменьшить это время. Стоимость использования шаблона состоит из двух вещей, время на парсинг заголовочного файла #include и время на инстанцирование шаблона для заданного типа. Когда обрабатывается юнит компиляции (файл .cpp), препроцессор загружает каждый заголовочный файл, который он находит в директивах #include этого файла и всех последующих хедеров. Заголовочные файлы обычно имеют защиту от рекурсии include guards, чтобы предотвратить повторную загрузку, поэтому каждый заголовок обычно загружается только один раз (обычно, потому что все равно можно словить скрытую рекурсию).

В отличие от кода без шаблонов, каждое инстанцирование, обращение и даже указатель на шаблонный класс требует компиляции всех его использованных и разыменованных членов. VS позволяет более вольно обращаться с этим правилом, но clang требует инстанцирования всего шаблона, а не только его частей.

В случае с большой иерархией включений например мегазаголовки, это может означать сотни (СОТНИ!) уникальных типов вектора в одном юните компиляции. Спасения здесь нет, даже то, что компилируются только те вещи, которые действительно используются, позволяют порезать время на проценты, но не в разы, так что если, например, метод std::vector::push_back никогда не вызывается и не компилируется, он все равно будет распаршен компилятором каждый раз и подготовлен для компиляции. А почему? А вот! Сделано это было для ускорения сборок, если такой метод уже есть в кеше компилятора, то его подготовка занимает меньшее время. И ребята из МС и кланга дружно подумали, а давайте мы будет заранее класть все встречающиеся методы в кеш, авось понадобятся.

Если используются 50 различных типов шаблонов вектора, то стоимость компиляции этих шаблонов оплачивается 50 раз. И это только для одной единицы трансляции, следующая единица трансляции снова платит за все это. !Профит. Давайте попробуем это исправить.

Forward Declaration

Если это возможно, надо использовать forward declarations. Это устраняет включение заголовочного файла, стоимость компиляции шаблона для конкретного типа. Не делать ничего — это лучшая оптимизация из тех, что я могу посоветовать. Шаблоны можно объявлять заранее, но тогда шаблон должен быть объявлен в хедере только через указатель или ссылку, и никогда не должен быть разыменован. Типы параметров шаблона также должны быть либо предварительно объявлены, либо полностью определены.

Иногда надо понять, что именно занимает время. Тут поможет флаг /d1reportTime компилятора для VS, на тестах мне иногда приходилось компилировать по одной строке за раз с минимальными изменениями и фиксировать время, которое было нужно компилятору, а потом думать почему то или иное изменение приводит к росту времени компиляции. Se la vi, как говорится ¯_(ツ)_/¯. Это конечно смешно было ловить блох, но вот вам пример:

``` void vector<_TYPE_>::preallocate(const size_t count) {   if (count> m_capacity) {                               // 0.000023s     _TYPE_ * const data = static_cast<_TYPE_*>                        (malloc(sizeof(_TYPE_) * count)); // 0.000076s     const size_t end = m_size;                           // 0.000028s     m_size = std::min(m_size, count);                    // 0.000402s     for (size_t i = 0; i < count; i++) {                 // 0.000042s        new (&data[i]) _TYPE_(std::move(m_data[i]));      // 0.000148s     } ```

Заметили что-нибудь необычное? А если так?

``` void vector<_TYPE_>::preallocate(const size_t count) {   if (count> m_capacity) {                               // 0.000023s     _TYPE_ * const data = static_cast<_TYPE_*>                        (malloc(sizeof(_TYPE_) * count)); // 0.000076s     const size_t end = m_size;                           // 0.000028s     if ( count < m_size ) { m_size = count; }            // 0.000012s     for (size_t i = 0; i < count; i++) {                 // 0.000042s        new (&data[i]) _TYPE_(std::move(m_data[i]));      // 0.000148s     } ```

Избыточность шаблонов

Дублирования в шаблонах достаточно много, но именно поэтому это и называется шаблоном, добавляя избыточность в одном файле мы убираем её во всех остальных местах
и платим за это временем компиляции. Каждый раз, когда шаблон инстанцируется с новым типом, все использованные члены компилируются заново, с единственное отличием — с другим типом. Ктор, дтор, копирование, операции перемещение, но остальная часть кода остаётся идентичной. И чем больше методов класса, тем дороже инстанцирование шаблона. А если еще появляются шаблонные функции шаблонного класса, время начинает лететь в космос! Каждая пустая шаблонная функция стоит около 0.000030 секунд на компиляцию, и это ещё до того, как в неё будет добавлен какой-либо код. Помещая вызов одной шаблонной функции в другую, мы сильно увеличиваем время компиляции и оно очень нелинейно меняется.

Здесь важно понимать, что хотя оптимизация шаблонных функций может привести к небольшим улучшениям, она не всегда будет лучшим решением, поскольку сама природа шаблонов приводит к значительным затратам на компиляцию из-за множества уникальных инстанцирований. В данном случае, нужно искать способы минимизировать количество инстанцирований или сократить количество шаблонных функций, которые компилятор должен обработать.

Анализ зависимостей и растаскивание иерархий хедеров позволило сэкономить три минуты, время сборки проекта стало 11:25с. На всю эту работу, протаскивание этих задач через таски ушло пару месяцев рабочего времени, проект большой, за раз все не починишь, плюс приходится согласовывать свои изменения с другими командами. Но результаты были видны и поэтому решили эту работу продолжить.

11 минут 25 секунд (Готовим precompiled headers)

>Build succeeded. >    0 Error(s) >Time Elapsed 00:11:25.210

Про include guards я думаю вы все знаете, иначе бы не пришли читать эту статью 🙂

Дальнейшее растаскивание хедеров не давало уже существенных результатов, +-5 секунд к времени билда не считаются. Если вы заметили, то на предыдущем скрине functional был во многих вызовах файлов. Это значит что проекту пришло время начать использовать PCH. Эта оптимизация была включена для большинства проектов, но для некоторых его частей выключена специально. Так как в тот код мне лезть не разрешили, поэтому особо там никакого прироста получить ну удалось, тем не менее минуту на существующем конфиге тоже сэкономили.

Как работают PCH

Если говорить в общих чертах, PCH создается путем компиляции исходного файла (*.cpp) с использованием специфичных для компилятора флагов. Когда заголовочные файлы из иерархии включений обрабатываются, они проходят через препроцессор как обычно, но их бинарный результат сохраняется на диск. Когда этот PCH используется в другом файле, его представление загружается, что позволяет пропустить многие шаги обработки и сэкономить время.

Преимущества PCH заключаются в том, что они позволяют значительно ускорить время компиляции, особенно при многократном включении одних и тех же заголовочных файлов
в разных единицах трансляции. Например, если вы часто используете стандартные заголовочные файлы или системные библиотеки, предварительная компиляция этих
файлов в PCH может существенно сэкономить время.

Недостаток PCH — это довольно дорогая операция, которая часто требует гораздо больше времени, чем преимущества, которые оно приносит. Каждое изменение в любом из заголовочных файлов, входящих в PCH, приводит к его пересборке. Второй неочевидный недостаток если вы включите слишком много хедеров начнут влиять уже чисто физические особенности работы с файлами, на скрине ниже наш общий PCH файл перевалил за 4Гб. Я не знаю, что там студия такого делает, чтобы получить такие объемы, но загрузка такого объема стала просто долгой по времени и сьела все преимущества, в этоге пришлось разделить проекты и каждому сделать свой отдельный PCH файл. Заодно получилось почистить сами PCH конфиги и снизить их объем до 1ГБ каждый, это позволило сэкономить еще где-то минуту c хвостиком.

10 минут 13 секунд (Упрощаем зависимости)

>Build succeeded. >    0 Error(s) >Time Elapsed 00:10:13.410

Чем больше заголовочных файлов включается, тем дольше происходит компиляция. Заголовок, который включает всё подряд, начитает тормозить всё вокруг вас. Системные заголовки закончились, и мы стали смотреть уже на время компиляции файлов движка и игры, одним из решений стало использовать глобальные хедеры с объявлением основных типов, которые используются. Это позволило не только упростить внутреннию иеррархию заголовоков, но и существенно порефакторить зависимости между модулями, которые и приводили к беспорядочным включениям. А заодно найти проблему с включением windows.h, в некоторых файлах, откуда он пролезал по всему проекту и увеличивал время компиляции. Избавление от сверх связаности в иерархии объектов и включения windows.h позволило сэкономить еще секунд 40.

9 минут 31 секунда (Оживляем PIMPL)

>Build succeeded. >    0 Error(s) >Time Elapsed 00:09:31.350

В C++ доступ к приватным членам класса получить несложно (https://habr.com/ru/articles/762250/) , хотя они и скрыты от внешнего кода. Компилятор лишь немного усложняет доступ к ним, однако для внешнего кода иногда необходимо знать о приватных переменных, например чтобы определить размер и выравнивание объекта. Часто классы включают приватные функции и данные, хотя на самом деле они в хедере не нужны или вообще вредны и тянут за собой другие хедеры. Если данные или функции используются только в реализации, то можно сделать их видимыми только в этом модуле, что приводит к нескольким преимуществам:

  • Меньше работы для препроцессора — минимизация заголовков.

  • Снижение времени линковки — меньше символов в глобальных таблицах символов.

Метод PIMPL (Pointer to Implementation — указатель на реализацию) помогает скрыть данные и функциональность, уменьшая изменения в публичных заголовочных файлах, что в свою очередь уменьшает время компиляции. Этот подход помогает «закрыть» детали реализации от внешнего мира, но имеет свои недостатки в виде дополнительных расходов на выделение памяти и обращение по указтелю. Но в определенных реализациях, где перф не стоит на первом месте мы можем позволить себе развязать зависимости там, где это не получается через forward declaration. Получилось не супер красиво, но позволило сэкономить еще 20 секунд. Решение получилось спорным, поэтому решили дальше его не развивать.

9 минут 12 секунд (Отключаем анализаторы)

>Build succeeded. >    0 Error(s) >Time Elapsed 00:09:12.130

Это ужасное предложение, не делайте так! Но если вас действительно беспокоит время компиляции, отключите анализ кода. Анализ значительно замедляет сборку… ооочень сильно. В нашем случае это занимало почти полторы минуты. Не делайте этого на своих проектах без весомых причин. Правильный код — это проверенный код, всем чем только можно, матрицей компиляции, десятком анализаторов и парочкой мудрых лидов. Неправильный код — весь остальной, быстро, но бесполезно и с ошибками. В итоге мы решили это на организационном уровне, PR запускались без анализатора кода, если PR компилился нормально и проходил минимальные тесты, то он уходил в дев, а билдферма запускала второй такой же, но уже с включенным анализом.

7 минут 34 секунды (Отключаем юниттесты)

>Build succeeded. >    0 Error(s) >Time Elapsed 00:07:34.440

И вот мы пришли почти к 7 минутам, это хорошее время комиляции для большого проекта, из почти трех десятков тысяч файлов. Анализируя время, которое было потрачено на компиляцию, стало понятно, что наш разросшийся с прошлого апдейта игры блок тестов, который на тот момент достиг почти 3к различных проверок, занимает почти минуту времени на сборку, тесты эти не нужны в повседневной сборке, и обычный разработчик их никогда не запускал, а время они тратили. Поэтому с ними поступили также как с анализом кода — вынесли в отдельный шаг на билдферме после прогона PR.

6 минут 22 секунды (Отключаем LTO)

>Build succeeded. >    0 Error(s) >Time Elapsed 00:06:22.240

Link Time Optimization (LTO) — это технология оптимизации, которая выполняется на этапе линковки приложения. Компилятор может оптимизировать код на уровне отдельного файла, но при использовании LTO компилятор анализирует сразу всю программу целиком. Это позволяет устранить неиспользуемые функции, более эффективно выстроить порядок вызовов и минимизировать накладные расходы. LTO разделяются на полное LTO (Full LTO), и Thin LTO. Thin LTO — это вариант, специально разработанный для ускорения линковки больших проектов, весь проект бьется на несколько частей и оптимизация проводится параллельно в каждой. LTO значительно увеличивает время компиляции, в нашем случае это занимало минуту с хвостиком для ThinLto, почти две минуты для Full. FullLTO по бенчмаркам давало прирост около 4%, ThinLTO — около 3%, полное оставили только для QA билдов, а всем остальным включили побыстрее. Итого еще минус минута на сборке.

5 минут 16 секунд (Финиш)

>Build succeeded. >    0 Error(s) >Time Elapsed 00:05:16.740

Итак финальное время сборки пять минут с хвостиком, считаю неплохо получилось. Не все конечно удалось решить только изменением в проекте, но пять минут, это пять минут — чашка кофе с круассаном, а не банка пива с бутером. Вообщем как обычно, потихоньку придумывали себе проблемы, а потом мужественно их чинили.

З.Ы.

Compiler Diagnostic
Для Microsoft Visual Studio существуют флаги, которые предоставляют информацию разной степени полезности:

  1. /Bt+ — сообщает время компиляции передней и задней части компилятора для каждого файла. C1XX.dll — это передняя часть компилятора, которая отвечает за компиляцию исходного кода в промежуточный язык (IL). Время компиляции на этом этапе обычно зависит от времени работы препроцессора (включения, шаблоны и т.д.). C2.dll — это задняя часть компилятора, которая генерирует объектные файлы (преобразует IL в машинный код).

  2. /d1reportTime — сообщает время работы передней части компилятора, доступно только в Visual Studio 2017 Community или более новых версиях. (Спасибо @phyronnaz и @aras_p)

  3. /d2cgsummary — сообщает о функциях, которые имеют «аномальные» времена компиляции. Это полезно, попробуйте использовать.

Комбинирование этих флагов в Visual Studio предоставляет много информации о том, куда уходит время компиляции. Для clang есть флаг -ftime-report, советую посмотреть этот пост (https://aras-p.info/blog/2019/01/16/time-trace-timeline-flame-chart-profiler-for-Clang/), он довольно старый но принципиально ничего не поменялось.

Если у вас есть дополнения и предложения — пишите в коментах. ccache/ram disk не предлагать, дорого, много возни и мало профита.

Спасибо, что дочитали!


ссылка на оригинал статьи https://habr.com/ru/articles/856916/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *