Оптимизация сборки крупного проекта

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

    В процессе разработки и дальнейшего развития крупного разнородного проекта мы также столкнулись с проблемами оптимизации. Но должны же быть способы уменьшить время сборки? Мы решили найти способ решения этой задачи. Собрать за 60 секунд!..



    О том, чего в итоге удалось добиться, и пойдет речь в нашем топике.

    Начало


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

    Вот, что мы имели в начале пути:

    • 176 проектов C++ и C# (в соотношении приблизительно 60% к 40%);
    • 100 МБ исходников;
    • 18 000 файлов (> 3 млн строк);
    • MSVS;
    • сервер (8 CPU, обычный диск на 10 рейде HDD, 12 ГБ ОЗУ);
    • виртуальная машина.

    Все это собиралось 12 минут.

    После оптимизации кода по результатам статического анализа, удаления лишних директив #include, перекомпоновки исполняемых модулей, динамических и статических библиотек время сборки уменьшилось до 8 минут. Далее речь пойдет именно об оптимизации процесса сборки, когда все возможные улучшения кода уже реализованы.

    Чтобы достичь максимальной скорости, мы использовали два подхода к оптимизации: аппаратный и программный. Как мы покажем далее, первый совсем не исключает второго, но дополняет его.

    Программный способ


    Типовые методы

    Про типовые методы настройки VisualStudio написано очень много на просторах Интернета, поэтому коснемся их вскользь.

    • Ключ /MP, который позволяет запускать несколько процессов компиляции одновременно.
    • Ключ /Z7, который отключает генерацию отладочной информации, в результате чего компиляция производится быстрее.

    Более интересна технология, известная как UnityBuild. Это один из самых эффективных программных методов оптимизации, ускоряющий сборку на 30%. В процессе работы мы слегка усовершенствовали эту технологию и назвали новую, улучшенную версию Smart UnityBuild.

    Стандартная технология работает следующим образом: в каждом файле *.cmake каждого проекта используется модуль unity_build.cmake, который направлен на папку, содержащую все *.cpp-файлы, обрабатывает их и объединяет в новый «общий» файл *.cpp. Далее создается файл *.vcproj, содержащий этот единый *.cpp.

    Наш вариант функционирует не совсем так. В некоторых случаях (в частности, при работе с большим проектом) создание одного общего *.cpp-файла является не самым оптимальным вариантом. Поэтому мы решили вместо одного большого генерировать несколько файлов.

    Схематически это выглядит так:



    В результате использование нашей технологии Smart UnityBuild позволило ускорить сборку до 4 мин. 40 сек.



    UnityBuild + PCH (Incredibuild)

    Очевидно, что использование PCH совместно с технологией UnityBuild, что называется в лоб, ничего не даст. Но все же есть способ обмануть VisualStudio. Для этого нужно искусственно вынести файлы, использующиеся в проекте несколько раз (у нас это *.boost и *.spl), в отдельный проект, собрать из него Precompiled Headers и подкладывать их в основной проект, в результате VisualStudio будет «думать», что это настоящие PCH. При использовании технологии Unity Build с Precompiled Headers и ключом LP можно добиться значительного сокращения времени сборки: в нашем случае — с 12 мин. до 4 мин. 38 сек. (без ключа LP — 5 мин. 53 сек.).

    Аппаратный способ


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

    SSD RAID0 x 7

    Наилучшего результата по времени сборки удалось добиться при использовании 7 дисков SSD в Raid0-массиве и RAMDisk: длительность сборки проекта составила около 6 минут.

    32 CPU x 3 GHz

    Прирост в быстродействии более чем в два раза по сравнению со стандартным значением времени (7 мин. 45 сек.) дает использование 32 процессоров с технологией Hyper Threading (3 мин. 22 сек.).



    Итог


    Соединив все средства от опций Visual Studio до использования SSD дисков, мы смогли добиться снижения времени сборки проекта с 12 минут до 1 мин. 45 сек. Таким образом, заявленная цель — уложиться в 60 секунд — осталась невыполненной, но мы не расстроились :) Снижение времени, затрачиваемого на сборку проекта, более чем в 6 раз — это хороший результат.

    Вот как выглядит прогресс сборки нашего проекта:



    Из диаграммы видно, что достижению еще большей скорости мешают несколько длительных по времени процессов. Это линковка больших модулей, распараллелить которую невозможно (только частично попытка сделать это есть в MSVS 2012), но можно разбить их программно на несколько мелких, уменьшить количество и размер статических библиотек.

    Вот, собственно, и все. Перед нами не стояло цели объять необъятное, и некоторые технические аспекты оптимизации не вошли в статью. Мы готовы раскрыть подробности и ответить на все вопросы (в том числе по исходникам cmake) в комментариях. Cпасибо за внимание!

    P. S. Мысли, изложенные в данном топике, легли в основу презентации, которую Виктор Стрелков (руководитель отдела исследований и контроля качества Positive Technologies) представил на конференции CEE-SECR в ноябре 2012 года. Запись трансляции этого выступления можно посмотреть на сайте мероприятия.
    Positive Technologies
    256.97
    Company
    Share post

    Comments 45

      +1
      100 МБ исходников;


      А в видео говорится про 500.
        +3
        Как вы внимательно смотрели видео :)
        Да, к сожалению, при подготовке презентации в данные вкралась ошибка. По старым данным получалось что строка кода у нас длиной под 300 символов :)
        +9
        Есть такая штука ccache, ускоряет сборку в разы а то и более…
        Вроде есть и под Win. Не пробовали ещё и её использовать?
        Имею ввиду не «вместо», а дополнительно… (т.к. у вас есть c++ код).
          0
          А в чем смысл? С таким же успехом можно просто clean не делать, и неизмененные файлы не будут комипилироваться.
            +1
            ccache более интеллектуальное и мощное средство
              0
              Не скажите. Ccache имеет неприятную особенность иногда ломать кэши. Под гентой бывает сборка пакеты вываливается с ошибкой об неверном формате объектника, после чистки кэша или отключения — всё ок. Не знаю, с чем именно это связано, но факт имеет место быть. Возможно коллизии хешей, возможно баг. А возможно у меня диск посыпался…
              +2
              ccache не только это кеширует. Если, например, есть общие исходники у разных проектов, то он будет кешировать объекты и такого плана.
              А если clean сделать, то он тоже ускоряет процесс, так как многие вещи не меняются.
              Я думаю, что есть смысл поэкспериментировать. Это не панацея, конечно, но попробовать есть смысл.
                –1
                Ну, если есть общие исходники у разных проектов, тоу вас есть более серьезная проблема, чем скорость сборки.
                Если только несколько конфигарций одновременно поддерживается. В общем, специфичная получается вещь.
              0
              Пробовали и используем. Действительно, в некоторых случаях работает более эффективно чем студийный build без clean. Для MSVS эта штука называется clcache, мы немного отредактировали ее первоначальный вариант отсюда https://github.com/frerich/clcache
              0
              Интересно, а нафига? У вас каждый раз меняются все 176 проектов, что их надо перекомпилировать? Из собственного опыта: около 300 проектов в работе, побиты логичненько на около 20 сольюшенов. В результате, если я поменял что-то — не нужно пересобирать все 300 — только группу проектов данного сольюшена — а это считанные секунды. Конечно, когда меняется какая-то корневая библиотека, от которой всё зависит — надо пересобрать всё. Но в проектах такого масштаба все низкоуровневые блоки уже давно написаны, меняются только высокоуровневые части.
                0
                А что у вас за софт который состоит из 300 проектов?
                  0
                  Софт как софт, мультемедию всякую колбасим. Деталей рассказывать не могу, но вот представьте себе, что у вас есть музыка и видео и все возможные действия с ними: играть, конвертить, редактировать, искать, загружать, захватывать, стримить, бекапить, записывать на диски, покупать, искать слова песен, узнавать песни по их кусочкам и еще 100 тыщ разных действий. При этом 300 проектов — фигня, у нас и по 500 бывает и по 1000.
                    0
                    Под проектом понимается отдельно взятая опция программы? Да даже программа с 500 опциями это уже перебор. Этакий швейцарский нож. А мы уже не раз читали что швейцарские ножи это плохо. Это десктоп или веб портал? если веб портал то почему на С++. Или у вас не С++?
                      0
                      Именно тот факт, что каждая опция программы по сути является подключаемым (или отключаемым) плагином, отдельной библиотекой и позволяет жить проекту такого масштаба. Может быть лайт-версия с десятком самых необходимых фич, может быть версия для профи, могут быть плагины, скачиваемые по требованию и т.д. И именно это позволяет ребилдить при правках не все 300 проектов, а только то, что нужно. Именно это позволяет не быть швейцарским ножом в среднем и всё-таки быть им тогда, когда это необходимо. В общем, проектик живет уже лет 15 и еще столько же проживет.
                        +1
                        По описанию похоже на аутсорс Nero :)
                0
                Ключ /Z7, который отключает генерацию отладочной информации, в результате чего компиляция производится быстрее.
                Разве не наоборот?
                /Z7 Produces an .obj file containing full symbolic debugging information for use with the debugger.
                Тоже раньше думал, что создание отладочной информации затратно, но после внедрения Symbol/Source сервера отлаживать релизные модули стало на порядок удобнее.
                  +1
                  /Z7 приводит к тому, что отладочная информация (при компиляции данного .cpp) помещается в соответствующий .obj-файл, а не в общий для всего проекта файл отладочной информации. Плюс в том, что в результате каждый из .cpp можно компилировать параллельно с остальными.
                  0
                  Странно, что рамдиск быстрее, чем одиночный SSD, и странно, что SSD от HDD почти не отличается.
                  Пробовал наш проект в различных вариациях, ~ 70 проектов в солюшене, размер исходников правда не мерял, бусты, апачевские и прочие библиотеки лежали внутри, с пребилдом, лень было отсеивать.
                  На SSD проект раза в 4 быстрее собирался, чем на HDD, а рамдиск медленнее чем SSD процентов на 30. Из чего сделал вывод, что чтение с SSD уже дает достаточную пропускную способность, а рамдиск только ресурсы у компилятора отнимает.
                    0
                    Возможно использовался standalone ram-диск. Делать ram-диск за счет оперативной памяти самого компьютера точно не рационально.
                      +3
                      Т.е. писать на диск, ждать — это рационально, а писать на рам-диск, который быстрее — не рационально?
                        0
                        В изначальном комментарии человеку показалось странным, что запись на рам-диск быстрее, чем SSD, хотя по его экспериментам выходило вроде как наоборот.
                          +1
                          Запись на RamDisk (не standalone) действительно быстрее, чем на SSD. Так что очень даже рационально за счет памяти самого компьютера.
                            0
                            Если тупо копировать файл, то да. А вот если компилировать — то тут уже все не так радужно. Просто приведу специально выпуклый пример — при равном hardware где быстрее скомпилируется крупный проект — на 8GB оперативной памяти и обычном HD или на 1GB оперативки с 7 GB рам-диском?
                              +1
                              Конечно же быстрее будет с 4-8Gb оперативки и 8GB рам-диском.
                                0
                                Если вы будете 8GB рам диск делать за счет своей оперативки компьютера, то компилироваться может не сильно быстрее, чем при использовании 16 GB памяти без рам-диска. Проверено на крупном проекте. Именно поэтому желательно использовать харжварный рам-диск (т.е. не софтварный).
                                  0
                                  Зависит от проекта. Если для компиляции достаточно 4GB — то 8GB рам-диск в основной памяти ускоряет сборку.
                      0
                      А сам RamDisk тестировали? Они же разные бывают и не все «одинаково полезны». Некоторые из них дают приемлемые результаты на определенных размерах кластера, и это не 4096.
                        0
                        Сам рамдиск(пробовал dataram) дает заоблачные показатели скорости копирования, 5гб в секунду влегкую.
                          0
                          Сам им пользуюсь, только кластер выставил 32кб, чтобы быстрее работало.
                          Но почему тогда:
                          Странно, что рамдиск быстрее, чем одиночный SSD
                          ?
                            0
                            Как я уже написал, считаю, что ПСП и процессорное время отнимает у компилятора. Там же не просто чтение из памяти, он файловую систему имитирует, драйверы и все такое.
                            А скорость чтения уже не узкое место, далее расширять не дает прироста.
                            У меня медленнее. 2.5 минуты против 3х с копейками.
                              0
                              У меня проект не слишком большой, и я не весь его держу на RamDrive, а только временные файлы компиляции там складируются и %TEMP%. Ускорение есть, но точные значения уже не помню. А какой у вас был размер кластера? Я смотрел сравнительные характеристики — на 4кб Dataram не ахти, видимо сказывается как раз оверхед файловой системы, а 32кб и выше — уже существенно лучше.
                      +2
                      Для наших проектов на C/C++ для Windows при достаточном количестве оперативки SSD сборку ускорял процентов на 15%. %Temp% на рам-диске дает больший выигрыш.
                        0
                        Кто подскажет, откуда диаграмма времени работы процессов? Полезная штука в хозяйстве, чем отслеживать визуально в ProcessMonitor.
                          +1
                          Если Вы имели ввиду последнее изображние из поста, то это лог IncrediBuild.
                            0
                            Жаль. Спасибо.
                          0
                          Только недавно интересовался подобной информацией относительно больших C++ проектов, но к сожалению информации не так много.
                          Было бы интересно узнать о той оптимизации кода, которую провели на начальном этапе.
                          На данный момент работаем над проектом, архитектура которого не самая удачная и требует реорганизации, но из-за размеров (более 300 проектов в солюшене) провести мгновенную оптимизацию невозможно. На текущий момент, пересборка проекта, с IncrediBuild занимает порядка 1.5 часа, иногда больше.
                          Может быть есть у кого подобный опыт, чтобы подсказать в какую сторону копать, и как постепенно привести проект к более лучшей организации, и как результат уменьшить время сборки?
                          P.S проект написан на C++, платформа Windows.
                            0
                            Дайте угадаю. В проекте используются шаблоны. Используются в неимоверном количестве?
                              0
                              Все относительно.
                              Конечно есть и шаблоны — много ли их? — встречаются, да и сам иногда пишу небольшие.
                              Вопрос в другом — в какую сторону смотреть, чтобы ситуацию улучшить, вот раньше упоминали модульность, которой как таковой в проекте сейчас немного, может есть у кого опыт постепенного обновления архитектуры проекта?
                              +1
                              Мы постараемся сделать подобное описание в одном из следующих постов.
                              +1
                              поделитесь опытом: каким инструментом вы пользовались для получения диаграммы процесса сборки?
                                0
                                Это похоже на IncrediBuild
                                  0
                                  да очень похоже — спасибо
                                0
                                да очень похоже — спасибо
                                  0
                                  дает использование 32 процессоров с технологией Hyper Threading

                                  Где вы взяли 8-ми процессорный сервер?!
                                  Где вы взяли машину на 32 процессора?
                                  Или это несколько серверов? (Тогда я не увидел, где написано, что вы перешли на распределенные технологии)
                                  Или это 32 ядра на 4-х процессорной машине?
                                    0
                                    Я тоже хочу 32-х процессорный сервер с Hyper Threading!!!

                                  Only users with full accounts can post comments. Log in, please.