Оптимизация сборки С++ проектов
Способы, поддержка и
последствия
1
План доклада 2
• Стартовые условия.
• Ускорение компиляции.
• Ускорение линковки.
• В каждом способе ускорения отдельно рассмотрим влияние на кодобазу и затраты на
поддержку.
Стартовые условия
3
Состояние кодобазы 4
• 10+ лет постоянного развития и добавления нового.
• Взрывной рост количества инженеров на проекте.
• Распределённая команда.
• Гайдлайны не успевают за фичами => разношёрстный код.
Основные кейсы для оптимизации 5
• Понять, в каком состоянии время сборки.
• Ускорение девелоперских итераций. Получение бинарника после правки одного .cpp.
• Full rebuild или крупные пересборки.
Как ускоряем?
• Ускоряем методиками, применимыми ко всему коду, без знания контекста.
• Проекты под Windows. Ориентация на них в первую очередь.
• Стремимся вырабатывать автоматические или полуавтоматические решения.
Зачем ускоряем?
Ускорение компиляции
6
Источник постоянных проблем 7
Способ достижения «модульности» не менялся много лет
Заголовочный файл
каждый раз
обрабатывается с нуля
// a.cpp
#include "vector3.hpp" // slow processing
// b.cpp
#include "vector3.hpp" // and again
// c.cpp
#include "vector3.hpp" // ...and again
// z10000.cpp
#include "vector3.hpp" // >_______<
8
Все не любят инклуды
Уменьшим затраты на обработку #include 9
• IWYU. Include-What-You-Use.
• PCH. Precompiled header.
• Unity Builds.
10
IWYU
IWYU 11
Include What You Use
// matrix.hpp
class Matrix{};
// matrix_to_ogg_converter.hpp
#include "matrix.hpp"
class YouDontNeedIt
{
Matrix m;
};
// your_header.hpp
#include "matrix_to_ogg_converter.hpp" // WHAT? FOR Matrix?!
#include "matrix.hpp"
class Some
{
Matrix m;
};
Как применить IWYU на существующем коде? 12
• Вручную конвертировать большую кодобазу почти невозможно.
• Давайте автоматизировать?
• Есть open-source решение – IWYU.
• Open-source требует доработки.
Эффект применения IWYU 13
• В ряде cpp выкинуло ряд не нужных для компиляции инклудов.
• Часть инклудов заменили на forward declaration.
• Время сборки некоторых крупных cpp, собиравшихся более 10 секунд, сократилось на 40%.
• В ходе работы инструмента можно собрать ряд полезной информации о зависимостях в
проекте.
Из хорошего
Эффект применения IWYU 14
• Внезапно, IWYU требует clang-компилируемую кодобазу.
• Результат ускорения пересборки всего клиента не сильно заметен, по сравнению с
компиляцией отдельных файлов. Основное подозрение – изначальное отсутствие
монолитных хедеров.
• Особенности некоторых third-party не позволяют полностью автоматически применять IWYU:
• Бывают «внутренние» заголовочные файлы.
• Иногда сам #include приводит к выполнению кода.
Из интересного
Так нужен ли IWYU? 15
• Да, т.к. отдельные файлы получают ускорение.
• Да, т.к. по отзывам обладателей монолитных хедеров (wot blitz, unreal) ускорение было 10+
процентов.
• Да, т.к. убираются откровенно ненужные связи между библиотеками.
• Visual Assist/Resharper упрощают вставку хедеров. Но их нужно перепроверять.
Практика и инструмент
IWYU - Затраты на поддержку 16
• Требуется постоянное ревью кода.
• Автоматизированного универсального способа валидации пока (?) нет.
• Сделать полностью автономный инструмент своими силами не самая простая задача.
• Существующий IWYU инструмент требует компилируемый clang код.
17
PCH
PCH 18
Precompiled Headers
Заголовочный файл
каждый раз
обрабатывается с нуля
// a.cpp
#include "vector3.hpp" // slow processing
// b.cpp
#include "vector3.hpp" // and again
// c.cpp
#include "vector3.hpp" // ...and again
// z10000.cpp
#include "vector3.hpp" // >_______<
PCH 19
Precompiled Headers
// pch.hpp
#include “vector3.hpp"
Тяжеловесная обработка
vector3.hpp только в
pch.hpp
// a.cpp
#include “pch.hpp"
#include "vector3.hpp"
// b.cpp
#include “pch.hpp"
#include "vector3.hpp“
// c.cpp
#include “pch.hpp"
#include "vector3.hpp"
// z10000.cpp
#include “pch.hpp"
#include "vector3.hpp"
Напишем PCH 20
• Понять, а какие third-party headers нужны для либы.
• Понять, какие наши headers использует либа.
• Скомпоновать из них такой PCH, который компилируется.
• Обработать сотню уже существующих либ. В нашем случае выкинуть ещё и старые pch.
• Периодически обновлять PCH
Что нужно для написания PCH?
Решение - Автоматика 21
• Cotire
• Требует Single Compilation Unit.
• Придётся допиливать под наши проекты.
• Собственный PCH-генератор
• Пишем для наших проектов, зная внутреннюю специфику
• Анализируем include graph
Оскал суровой реальности 22
• В инклуд графе есть .inl, .ipp и прочий кошмар.
• Некоторые заголовочные файлы нельзя включать вручную.
• Некоторые заголовочные файлы работают только в определённом порядке.
• Все собственные заголовочные файлы вставлять тоже не стоит.
Решение
• White list хедеров (“мега-pch”), являющийся фильтром include graph.
Headers так просто в PCH не лезут.
Негативные последствия 23
• Очевидное: слишком связные PCH приводят к более частой рекомпиляции.
• Менее очевидное, но болезненное: с течением времени кодобаза становится более
хрупкой.
• Устанавливаются дополнительные связи в коде. Рассмотрим примеры.
Типовые дефекты в инклудах
24
1. Cpp забыл часть заголовочных файлов 25
Ряд зависимостей вставляется только через pch.hpp
// a.cpp
#include "pch.hpp"
#include <string>
#include <vector>
std::string coolFunc(std::vector<int> a)
{
return "";
}
2. Header содержит неполный набор зависимостей 26
Как добавить веселья коллеге
// a.hpp
#pragma once
#include <string>
class B;
B* coolFactory(std::string name);
// a.cpp
#include "pch.hpp"
#include "a.hpp"
#include “b.hpp”
B* coolFactory(std::string name)
{
return new B();
}
3. ifdef в header без дефайна 27
«Я случайно флаг»
// a.hpp
#pragma once
#include "debug_flag.hpp"
#ifdef SOME_DEBUG_FLAG
#include "debug_flag.hpp" // WHY?!
int myCoolDebugFunc();
#endif
// any other user cpp
#include "pch.hpp"
#include "a.hpp"
#include "debug_flag.hpp"
// debug_flag.hpp
#pragma once
#define SOME_DEBUG_FLAG 1
Есть ли выход из этой ситуации? 28
Почему всё так? 29
• Environment позволяет сделать «случайно» компилирующиеся .cpp.
• Не обращали внимания, пока проблема не стала слишком большой.
• Ни компилятор, ни инструменты типа VAssist/Resharper не показывают ошибки при инклудах
через pch.
• На старте работ по автоматизации PCH пришлось руками разгребать накопившиеся
проблемы. IWYU помог в улучшении ситуации.
Self-contained headers! 30
Заголовочные файлы, компилирующиеся без посторонней помощи в .cpp
// self_contained.hpp
#pragma once
#include <debug_flag.hpp>
#include <vector>
class B;
#if SOME_DEBUG_FLAG
std::vector<int> createSequence(const B& b);
#endif
// ANY .cpp
#include "self_contained.hpp“ // The First
Эффект от применения 31
• 15% ускорения full rebuild в сравнении с написанными вручную.
• Более чем двукратное (x2) ускорение в сравнении с отсутствием PCH.
• Наличие генератора с заранее описанным алгоритмом снимает головную боль при
написании новых pch для новых либ.
Затраты на поддержку 32
• Дополнительная nightly проверка.
• Проверка пытается собрать клиент с отключёнными PCH. Ошибки компиляции/линковки –
валидация не пройдена.
• Периодически нужно актуализировать pch.
Требуется дополнительный контроль за pch-зависимостями
Unity Builds 33
• Ещё более ярко выражены плюсы и минусы в сравнении с PCH.
• Требуют модификации кода, специфичного только для Unity Builds:
• Убрать одинаковые static символы и символы в анонимных неймспейсах.
• #undef макросов.
• Убрать using на весь .cpp.
• Порядок следования .cpp также имеет значение.
• Потенциальное замедление мелких итераций (сборка нескольких .cpp вместо одного).
• Следствие: в нашем проекте (пока?) не используем, т.к. требует дополнительных затрат на
внедрение и поддержку с неясными перспективами.
Ускорение линковки
34
Почему важно ускорить? 35
• При мелких итерациях может быть в несколько раз дольше компиляции.
• Практически константна по времени независимо от .cpp.
• Даже мельчайшая итерация занимала больше минуты, а то и полутора. Из них более минуты
– линковка.
Инкрементальная линковка 36
• Отключить любые оптимизации линковки.
• Отключить /GL опцию компилятора. Опция вирусная, любое появление ломает
инкрементальную линковку.
• Отключить mt.exe.
Как сделать (MSVC)
Инкрементальная линковка 37
• Ускорение малых итераций в несколько раз при срабатывании.
• Не всегда срабатывает. (Новый .obj – full relink, например).
• Увеличение размера бинарников (у нас – двукратно).
• Падение производительности (у нас -10% FPS).
• Как следствие, непригодна для QA и для хранения.
• Но вполне ОК для локальной работы.
Эффект
Инкрементальная линковка 38
• Нужно иметь два варианта сборки бинарников: локальная сборка и обычная, для билд-
машин.
• Помнить про /GL опцию и не допускать её наличия в варианте «локальной сборки».
• /VERBOSE:INCR
Затраты на поддержку
FastLink 39
• Доехала в VS2017.
• Экономит за счёт убирания переноса данных в pdb.
• Меньше требований к опциям компилятора.
MSVC-specific
FastLink 40
• Работает устойчиво.
• Ухудшения производительности не обнаружено.
• Можно и нужно комбинировать с инкрементальной линковкой.
• Иногда в дебаге наблюдаются задержки в получении информации о символах.
Эффект
FastLink 41
• Две сборки (локальная и для билдмашин).
• Помнить, что pdb, собранный при FastLink, не подходит для хранения в сервере символов.
Затраты на поддержку
Что осталось за скобками 42
• Распутывание зависимостей. Творческий и нудный процесс одновременно.
• Extern template? – В теории должен помогать, но непонятно, к чему подступиться.
• Распределённая сборка – IncrediBuild (~6:30 vs ~5 минут).
Выводы
43
Выводы 44
• Практика IWYU полезна как для скорости сборки, так и для контроля зависимостей.
• Нужно следовать практике self-contained headers.
• PCH очень полезны для ускорения сборки, но нужно оберегать кодобазу от паразитных
связей из-за них.
• Unity Builds – up to you. Как PCH, но с ещё более ярко выраженными плюсами и минусами.
• Линковку можно радикально ускорить для локальных сборок.
• Без внутренних гайдлайнов по этим вопросам поддержка ухудшается.
• Без автоматизированных проверок эти процедуры тем более постепенно протухают.
• Ручная обработка крупной кодобазы почти нереальна.
45
Вопросы?
Александр Жоров, Senior Engine Developer, Wargaming.net
Email: a_jorov@wargaming.net
Telegram: @SlideGauge

C++ CoreHard Autumn 2018. Ускорение сборки C++ проектов, способы и последствия - Александр Жоров

  • 1.
    Оптимизация сборки С++проектов Способы, поддержка и последствия 1
  • 2.
    План доклада 2 •Стартовые условия. • Ускорение компиляции. • Ускорение линковки. • В каждом способе ускорения отдельно рассмотрим влияние на кодобазу и затраты на поддержку.
  • 3.
  • 4.
    Состояние кодобазы 4 •10+ лет постоянного развития и добавления нового. • Взрывной рост количества инженеров на проекте. • Распределённая команда. • Гайдлайны не успевают за фичами => разношёрстный код.
  • 5.
    Основные кейсы дляоптимизации 5 • Понять, в каком состоянии время сборки. • Ускорение девелоперских итераций. Получение бинарника после правки одного .cpp. • Full rebuild или крупные пересборки. Как ускоряем? • Ускоряем методиками, применимыми ко всему коду, без знания контекста. • Проекты под Windows. Ориентация на них в первую очередь. • Стремимся вырабатывать автоматические или полуавтоматические решения. Зачем ускоряем?
  • 6.
  • 7.
    Источник постоянных проблем7 Способ достижения «модульности» не менялся много лет Заголовочный файл каждый раз обрабатывается с нуля // a.cpp #include "vector3.hpp" // slow processing // b.cpp #include "vector3.hpp" // and again // c.cpp #include "vector3.hpp" // ...and again // z10000.cpp #include "vector3.hpp" // >_______<
  • 8.
  • 9.
    Уменьшим затраты наобработку #include 9 • IWYU. Include-What-You-Use. • PCH. Precompiled header. • Unity Builds.
  • 10.
  • 11.
    IWYU 11 Include WhatYou Use // matrix.hpp class Matrix{}; // matrix_to_ogg_converter.hpp #include "matrix.hpp" class YouDontNeedIt { Matrix m; }; // your_header.hpp #include "matrix_to_ogg_converter.hpp" // WHAT? FOR Matrix?! #include "matrix.hpp" class Some { Matrix m; };
  • 12.
    Как применить IWYUна существующем коде? 12 • Вручную конвертировать большую кодобазу почти невозможно. • Давайте автоматизировать? • Есть open-source решение – IWYU. • Open-source требует доработки.
  • 13.
    Эффект применения IWYU13 • В ряде cpp выкинуло ряд не нужных для компиляции инклудов. • Часть инклудов заменили на forward declaration. • Время сборки некоторых крупных cpp, собиравшихся более 10 секунд, сократилось на 40%. • В ходе работы инструмента можно собрать ряд полезной информации о зависимостях в проекте. Из хорошего
  • 14.
    Эффект применения IWYU14 • Внезапно, IWYU требует clang-компилируемую кодобазу. • Результат ускорения пересборки всего клиента не сильно заметен, по сравнению с компиляцией отдельных файлов. Основное подозрение – изначальное отсутствие монолитных хедеров. • Особенности некоторых third-party не позволяют полностью автоматически применять IWYU: • Бывают «внутренние» заголовочные файлы. • Иногда сам #include приводит к выполнению кода. Из интересного
  • 15.
    Так нужен лиIWYU? 15 • Да, т.к. отдельные файлы получают ускорение. • Да, т.к. по отзывам обладателей монолитных хедеров (wot blitz, unreal) ускорение было 10+ процентов. • Да, т.к. убираются откровенно ненужные связи между библиотеками. • Visual Assist/Resharper упрощают вставку хедеров. Но их нужно перепроверять. Практика и инструмент
  • 16.
    IWYU - Затратына поддержку 16 • Требуется постоянное ревью кода. • Автоматизированного универсального способа валидации пока (?) нет. • Сделать полностью автономный инструмент своими силами не самая простая задача. • Существующий IWYU инструмент требует компилируемый clang код.
  • 17.
  • 18.
    PCH 18 Precompiled Headers Заголовочныйфайл каждый раз обрабатывается с нуля // a.cpp #include "vector3.hpp" // slow processing // b.cpp #include "vector3.hpp" // and again // c.cpp #include "vector3.hpp" // ...and again // z10000.cpp #include "vector3.hpp" // >_______<
  • 19.
    PCH 19 Precompiled Headers //pch.hpp #include “vector3.hpp" Тяжеловесная обработка vector3.hpp только в pch.hpp // a.cpp #include “pch.hpp" #include "vector3.hpp" // b.cpp #include “pch.hpp" #include "vector3.hpp“ // c.cpp #include “pch.hpp" #include "vector3.hpp" // z10000.cpp #include “pch.hpp" #include "vector3.hpp"
  • 20.
    Напишем PCH 20 •Понять, а какие third-party headers нужны для либы. • Понять, какие наши headers использует либа. • Скомпоновать из них такой PCH, который компилируется. • Обработать сотню уже существующих либ. В нашем случае выкинуть ещё и старые pch. • Периодически обновлять PCH Что нужно для написания PCH?
  • 21.
    Решение - Автоматика21 • Cotire • Требует Single Compilation Unit. • Придётся допиливать под наши проекты. • Собственный PCH-генератор • Пишем для наших проектов, зная внутреннюю специфику • Анализируем include graph
  • 22.
    Оскал суровой реальности22 • В инклуд графе есть .inl, .ipp и прочий кошмар. • Некоторые заголовочные файлы нельзя включать вручную. • Некоторые заголовочные файлы работают только в определённом порядке. • Все собственные заголовочные файлы вставлять тоже не стоит. Решение • White list хедеров (“мега-pch”), являющийся фильтром include graph. Headers так просто в PCH не лезут.
  • 23.
    Негативные последствия 23 •Очевидное: слишком связные PCH приводят к более частой рекомпиляции. • Менее очевидное, но болезненное: с течением времени кодобаза становится более хрупкой. • Устанавливаются дополнительные связи в коде. Рассмотрим примеры.
  • 24.
  • 25.
    1. Cpp забылчасть заголовочных файлов 25 Ряд зависимостей вставляется только через pch.hpp // a.cpp #include "pch.hpp" #include <string> #include <vector> std::string coolFunc(std::vector<int> a) { return ""; }
  • 26.
    2. Header содержитнеполный набор зависимостей 26 Как добавить веселья коллеге // a.hpp #pragma once #include <string> class B; B* coolFactory(std::string name); // a.cpp #include "pch.hpp" #include "a.hpp" #include “b.hpp” B* coolFactory(std::string name) { return new B(); }
  • 27.
    3. ifdef вheader без дефайна 27 «Я случайно флаг» // a.hpp #pragma once #include "debug_flag.hpp" #ifdef SOME_DEBUG_FLAG #include "debug_flag.hpp" // WHY?! int myCoolDebugFunc(); #endif // any other user cpp #include "pch.hpp" #include "a.hpp" #include "debug_flag.hpp" // debug_flag.hpp #pragma once #define SOME_DEBUG_FLAG 1
  • 28.
    Есть ли выходиз этой ситуации? 28
  • 29.
    Почему всё так?29 • Environment позволяет сделать «случайно» компилирующиеся .cpp. • Не обращали внимания, пока проблема не стала слишком большой. • Ни компилятор, ни инструменты типа VAssist/Resharper не показывают ошибки при инклудах через pch. • На старте работ по автоматизации PCH пришлось руками разгребать накопившиеся проблемы. IWYU помог в улучшении ситуации.
  • 30.
    Self-contained headers! 30 Заголовочныефайлы, компилирующиеся без посторонней помощи в .cpp // self_contained.hpp #pragma once #include <debug_flag.hpp> #include <vector> class B; #if SOME_DEBUG_FLAG std::vector<int> createSequence(const B& b); #endif // ANY .cpp #include "self_contained.hpp“ // The First
  • 31.
    Эффект от применения31 • 15% ускорения full rebuild в сравнении с написанными вручную. • Более чем двукратное (x2) ускорение в сравнении с отсутствием PCH. • Наличие генератора с заранее описанным алгоритмом снимает головную боль при написании новых pch для новых либ.
  • 32.
    Затраты на поддержку32 • Дополнительная nightly проверка. • Проверка пытается собрать клиент с отключёнными PCH. Ошибки компиляции/линковки – валидация не пройдена. • Периодически нужно актуализировать pch. Требуется дополнительный контроль за pch-зависимостями
  • 33.
    Unity Builds 33 •Ещё более ярко выражены плюсы и минусы в сравнении с PCH. • Требуют модификации кода, специфичного только для Unity Builds: • Убрать одинаковые static символы и символы в анонимных неймспейсах. • #undef макросов. • Убрать using на весь .cpp. • Порядок следования .cpp также имеет значение. • Потенциальное замедление мелких итераций (сборка нескольких .cpp вместо одного). • Следствие: в нашем проекте (пока?) не используем, т.к. требует дополнительных затрат на внедрение и поддержку с неясными перспективами.
  • 34.
  • 35.
    Почему важно ускорить?35 • При мелких итерациях может быть в несколько раз дольше компиляции. • Практически константна по времени независимо от .cpp. • Даже мельчайшая итерация занимала больше минуты, а то и полутора. Из них более минуты – линковка.
  • 36.
    Инкрементальная линковка 36 •Отключить любые оптимизации линковки. • Отключить /GL опцию компилятора. Опция вирусная, любое появление ломает инкрементальную линковку. • Отключить mt.exe. Как сделать (MSVC)
  • 37.
    Инкрементальная линковка 37 •Ускорение малых итераций в несколько раз при срабатывании. • Не всегда срабатывает. (Новый .obj – full relink, например). • Увеличение размера бинарников (у нас – двукратно). • Падение производительности (у нас -10% FPS). • Как следствие, непригодна для QA и для хранения. • Но вполне ОК для локальной работы. Эффект
  • 38.
    Инкрементальная линковка 38 •Нужно иметь два варианта сборки бинарников: локальная сборка и обычная, для билд- машин. • Помнить про /GL опцию и не допускать её наличия в варианте «локальной сборки». • /VERBOSE:INCR Затраты на поддержку
  • 39.
    FastLink 39 • Доехалав VS2017. • Экономит за счёт убирания переноса данных в pdb. • Меньше требований к опциям компилятора. MSVC-specific
  • 40.
    FastLink 40 • Работаетустойчиво. • Ухудшения производительности не обнаружено. • Можно и нужно комбинировать с инкрементальной линковкой. • Иногда в дебаге наблюдаются задержки в получении информации о символах. Эффект
  • 41.
    FastLink 41 • Двесборки (локальная и для билдмашин). • Помнить, что pdb, собранный при FastLink, не подходит для хранения в сервере символов. Затраты на поддержку
  • 42.
    Что осталось заскобками 42 • Распутывание зависимостей. Творческий и нудный процесс одновременно. • Extern template? – В теории должен помогать, но непонятно, к чему подступиться. • Распределённая сборка – IncrediBuild (~6:30 vs ~5 минут).
  • 43.
  • 44.
    Выводы 44 • ПрактикаIWYU полезна как для скорости сборки, так и для контроля зависимостей. • Нужно следовать практике self-contained headers. • PCH очень полезны для ускорения сборки, но нужно оберегать кодобазу от паразитных связей из-за них. • Unity Builds – up to you. Как PCH, но с ещё более ярко выраженными плюсами и минусами. • Линковку можно радикально ускорить для локальных сборок. • Без внутренних гайдлайнов по этим вопросам поддержка ухудшается. • Без автоматизированных проверок эти процедуры тем более постепенно протухают. • Ручная обработка крупной кодобазы почти нереальна.
  • 45.
    45 Вопросы? Александр Жоров, SeniorEngine Developer, Wargaming.net Email: a_jorov@wargaming.net Telegram: @SlideGauge