Pull to refresh

Ускоряем Visual Studio, часть I. Unity Builds

Reading time 4 min
Views 19K
Original author: Oliver (OJ) Reeve
Это перевод статьи Oliver Reeve об одном из способов ускорения компиляции проекта. Автору удалось ускорить компиляцию с 55 до 6 минут. В своём проекте я получил прирост производительности около 22% (около минуты). Это не столь поразительно, как достижения автора, но всё же, умножив эту минуту на количество компиляций в день, количество разработчиков и длительность разработки проекта, я получил экономию, которая точно оправдывает затраты на чтение статьи и настройку проектов. Описано решение для Visual Studio и С++, но идея применима и к другим IDE, компиляторам и языкам программирования (не всем). В следующей статье я рассмотрел еще пару способов ускорения компиляции решения.


Каждый, кто работал над большим проектом на С++ (или С) ощутил на себе весь ужас длительного времени компиляции. Первый «большой» проект на С++, над которым я работал, собирался 10 минут. Это было намного дольше всего, с чем я работал ранее. Позже в моей карьере, когда я присоединился к индустрии разработки игр, я ощутил проблему компиляции по-настоящему большого проекта. Наша игра собиралась около часа.

И вот оно решение, которое мы внедрили: Unity Builds (далее — UB).

Некоторые читатели, хорошо знакомые с С и С++ могут решить, что то, что я опишу далее — это какой-то «хак». У меня нет конкретного мнения по этому поводу. Конечно, в идее есть нечто «хакообразное», но она действительно решает проблему длительной компиляции на большинстве платформ и для большинства компиляторов.

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


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

Базовый принцип UB достаточно прост. Он состоит в сокращении:
  • количества открытий каждого отдельного файла исходников.
  • количества файлов, которые открываются при компиляции.

Каждый исходный файл (.c, .cxx, .cc, .cpp и т.д.) компилируется в объектный файл. Когда все исходные файлы компилируются в объектные, линкер собирает все объектные файлы и создаёт из них исполняемую программу. Для тех из вас, кто не знает: чтение\запись на диск это наиболее медленная операция, которая и отнимает у компилятора больше всего времени (дело даже не столько в чтении и записи, сколько во времени поиска (позиционирования), но это для нас не суть важно). Таким образом, если мы хотим уменьшить время компиляции — хорошо бы начать с уменьшения количества операций ввода\вывода.

Я не хочу превращать эту статью в урок по тому, как компилируются программы на С++, так что напомню лишь некоторые детали. Мы знаем, что препроцессор парсит все файлы перед их компиляцией (а для этого он их открывает и читает). Позже компилятор снова открывает уже пропарсенные файлы. Создание и запись каждого объектного файла — снова потеря времени. И, наконец, линкер, с его процедурами чтения объектных и создания исполняемых файлов. Это всё занимает до чертей времени.

И вот он способ существенно всё это дело ускорить: мы не будем компилировать каждый отдельный файл исходного кода в проекте. Да, не будем. Вместо этого мы создадим мастер-файл, в который с помощью директивы include включим все остальные файлы в проекте. Вот этот мастер-файл мы и будем компилировать.

Теперь попробуем продемонстрировать идею в действии в Visual Studio (тем не менее, принцип может быть использован и в других IDE и с другими компиляторами). Первым шагом будет создание отдельной конфигурации сборки, на которой мы и будем экспериментировать без риска сломать стандартную процедуру сборки. Открываем Configuration Manager, жмем New Config и создаём новую конфигурацию, основанную на нашей конфигурации Debug (назовем её UnityDebug). Далее мы добавим в проект мастер-файл (назовем его UnityBuild.cpp) и включим с помощью директивы include в него все остальные файлы исходного кода проекта.

Если мы попытаемся скомпилировать проект в данный момент, мы закономерно получим кучу сообщений о дублировании различных классов, функций и других вещей. Чтобы это исправить, исключим из списка компилирующихся файлов в конфигурации UnityDebug все файлы, кроме UnityBuild.cpp, а в остальных конфигурациях — файл UnityBuild.cpp. (Для исключения файла из списка компиляции нужно открыть его свойства, найти пункт «Excluded From Build» и выставить его в «Yes» для всех нужных конфигураций).

У нас почти всё готово и мы можем попробовать скомпилировать решение. Далее вариантов у нас два: либо всё скомпилируется, либо нет. В первом случае я Вас поздравляю — Вы уже можете оценить прирост производительности. Во втором случае придется подумать. Думать нужно начинать с понимания того, что при нашей схеме сборки препроцессор собирает все файлы исходников в один большой файл. Таким образом, могут вылезти ошибки, связанные с глобальными и статическими переменными и функциями, названия которых могут кое-где повторяться. По-хорошему, у Вас ведь таких вещей должно быть немного, правда? Ну вот будет лишний повод уменьшить их количество и влияние друг на друга.

Теперь давайте оценим прирост производительности. Когда UB был внедрен в одной геймдев-компании, где я работал, время полной компиляции уменьшилось с 55 минут до 6-ти. В другом случае время изменилось с 10 минут до чуть более двух.

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

И напоследок вот ссылка на видео, в котором показано на практике всё, о чем было рассказано в статье.
Tags:
Hubs:
+22
Comments 28
Comments Comments 28

Articles