Как стать автором
Обновить
0
Pixonic
Developing and publishing games since 2009

Проблемы скорости сборки, или что делать, если время сборки игрового билда увеличивается в 10 раз

Время на прочтение8 мин
Количество просмотров5.2K

Разработка игр с многотысячной пользовательской базой и постоянно накатывающимися обновлениями — комплексный процесс. Он включает в себя не только работу над новым контентом и фичами, но и оптимизацию процессов, чтобы все происходило гладко, и разработчики, аналитики, QA-инженеры постоянно получали новые билды. 

DevOps — это и есть набор методов, благодаря которым можно сократить время разработки и ускорить выпуск обновлений. А CI/CD, или непрерывная интеграция и доставка — это не сколько технология, сколько целая культура, позволяющая чаще и надежнее производить небольшие изменения в игре с частыми коммитами, доставлять модули проекта в разные отделы и автоматизировать их тестирование. Все это представляет собой целый пласт работ — эдакий невидимый игрокам фронт.

Изначально, до старта работ над War Robots Remastered, у нас уже был выстроенный пайплайн CI/CD для всех проектов, и оригинальная War Robots не была исключением. Сам проект тогда в среднем собирался 40-100 минут. Но чем дальше продвигалась работа над ремастером, чем больше накапливалось проблем со скоростью сборок. Спустя полгода проект стал собираться от 3-х часов и больше с периодическими зависаниями, которые могли доходить до 7-10 часов. Это становилось совсем неприемлемым: QA в динамике не могли проверять билды, разработчикам тоже приходилось тратить время на ожидание, чтобы посмотреть результат или начать профилировать. Пришлось серьезно подумать над тем, как все это чинить и возвращать время сборок к исходному значению.

Как было раньше и какие проблемы это повлекло

Раньше наш пайплайн сборки приложения выглядел следующим образом. В качестве сервера CI мы использовали TeamCity от JetBrains: на War Robots тогда было выделено 22 агента, которые могли выполнять скрипты сборки. Они располагались на четырех физических компьютерах-нодах. Конфигурации нод приблизительно похожи, средняя тачка имела следующие характеристики:

  • OS: Windows;

  • ЦП: AMD Ryzen Threadripper 1950X 16-Core Processor 3,40 ГГц;

  • RAM: 128 ГБ;

  • Диски: SSD не менее 1ТБ на одного агента и не более двух агентов на один диск;

Для сервера Unity Cache V1 мы использовали:

  • VM: Linux CentOS 7;

  • RAM: 24 ГБ;

  • Диск: 150 ГБ.

Дополнительно был установлен Mac mini для сборок на iOS. Более подробно о старом пайплайне можно прочитать здесь.

Агенты у нас запущены как служба, работающая фоново независимо от пользователя, а не как отдельное приложение. Сделано это для возможности более гибкого менеджмента непосредственно самих агентов. Кроме того, таким образом можно выделить для каждого агента отдельного юзера со своими правами и реестром. Это оказалось удобно, но породило проблему, которую оказалось не так-то просто диагностировать. 

Заключалась она в том, что во время сборки Unity неожиданно начинала крешиться без особых опознавательных логов. Но небольшой ресерч в Интернете помог определить, что проблема связана с desktop heap, или кучей рабочего стола на Windows. 

В чем дело? У Windows есть неочевидная настройка: чем больше запущено процессов одновременно, тем быстрее происходит переполнение кучи. И поскольку у нас параллельно запускается несколько Unity для сборки билдов, это очень сильно влияло на скорость переполнения. 

Экспериментально увеличив значение  HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\SubSystems\Windows, креши удалось локализовать. Но с этим стоит быть аккуратнее: Microsoft не рекомендует выделять на кучу памяти больше, чем 20480 КБ:

Если вы выделяете слишком много памяти на кучи рабочего стола, может возникнуть отрицательная производительность. Поэтому мы не рекомендуем устанавливать значение более 20480 КБ.

Больше пресетов графики — дольше собирается билд. Что делать?

Как уже говорилось выше, по итогу время сборки билда выросло критически: с 40-100 минут до 3-4 часов с периодическими зависаниями импорта до 10 часов. Связано это с тем, что мы разделили текущий пресет качества графики и добавили новые. Если изначально у нас был только один пресет — Legacy, то теперь их стало четыре: Legacy, ULD (Ultra-Low Definition), LD (Low Definition) и HD (High Definition).

Разница между пресетами ULD, LD, HD:

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

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

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

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

Hashing assets (38480 files)... 66.051 seconds
  file read: 37.856 seconds (42992.805 MB)
  wait for write: 22.787 seconds (I/O thread blocked by consumer, aka CPU bound)
  wait for read: 9.976 seconds (CPUT thread waiting for I/O thread, aka disk bound)
  hash: 53.298 seconds

Они не говорят ни о чем плохом — лишь показывают, что система начала хешировать ассеты для последующих действий над ними. В нашем случае проблема была в том, что такое сообщение появлялось в логе после импорта каждого пятого-десятого ассета. Полный рехеш одного ассета выполняется за минуту, так что время импорта взлетело до небес. Обычно такое происходит, когда что-то в коде вызывает AssetDatabase.Refresh() — именно из-за него перехватчики других событий зацикливаются, и Unity приходится пересчитывать весь ассет полностью. 

Мы нашли все такие места — которых, конечно, оказалось немало, ведь эта функциональность нужна многим плагинам, — и обложили их трейсами. Затем мы нашли, где происходили рефреши — это оказалась подписка на AssetPostprocessor.OnPostprocessAllAssets. Так мы выяснили, что комбинация нашего скрипта и GPGSUpgrader (Google Play third-party файл) дает тот самый эффект увеличения времени сборки. 

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

Следующим нашим шагом была оптимизация текущего кэш-сервера Unity.

Мало кэш-серверов = большие нагрузки

Когда мы создаем проект в Unity, нельзя просто добавить в него ассет — Unity переформатирует его в собственные форматы отдельно для iOS и Android. Для хранения таких файлов и существует кэш-сервер, откуда можно запросить уже собранные и переформатированные ассеты, и их не нужно заново пересчитывать. Если ассеты на сервере не хранятся — в таком случае происходит их пересчет, и он занимает какое-то время.

Для справки приведем немного цифр нашей конфигурации проекта:

  • Размер репозитория — более 150 ГБ;

  • Общее количество веток в репозитории — более 1000;

  • Количество файлов в проекте — более 170 000, из них большая часть — ассеты. 

На момент старта работы над War Robots Remastered у нас было использовано три кэш-сервера на весь проект: один использовался разработчиками, остальные — для нашего окружения CI и Dev/Release. 

Проблема заключалась в том, что проект имеет очень большое количество мелких файлов, так что сервер не выдерживал нагрузки и просто не успевал их вовремя раздавать. Редко возникали ошибки сети, гораздо чаще — ошибки типа «Disk I/O is overloaded» из-за огромного количества обращений к диску. Из-за этого — те самые зависания на 5-7 часов, вызванные тем, что при невозможности кэш-сервера отдать файлы клиентам Unity начинала полный импорт проекта.

В результате мы пришли к тому, чтобы увеличить число кэш-серверов для CI для перераспределения нагрузки на дисках. Таким образом, 22 агента мы развели на сеть серверов — по три на каждую платформу: Android-Dev, iOS-Dev, Win-Dev, Android-Release, iOS-Release, Win-Release, — что значительно улучшило ситуацию. Также мы провели 10-гигабитную сеть до агентов. В результате проблемы сети перестали появляться, а количество ошибок I/O значительно снизилось. 

Но все же время импорта оставляло желать лучшего. В среднем сам импорт с кэш-сервера достигал полутора часов, и мы решили провести эксперимент с новой версией кэш-сервера — Unity Accelerator

На момент начала наших экспериментов у нас использовалась версия Unity 2018.4, которая не позволяла использовать Asset Database V2 — а он был необходим для того, чтобы использовать Unity Accelerator, поскольку формат хранения ассетов в нем был несовместим с версией Unity Cache Server V1. Параллельно нам пришлось обновлять версию Unity до 2019.4.22f1 — тогда-то и получилось совершить переход на новый кэш-сервер, который пошел проекту только на пользу. Теперь импорт с платформы iOS с очисткой папки Library выглядел следующим образом:

Таким образом, Unity Accelerator оказался вполне рабочим вариантом: время импорта у него приемлемое, но прогрев не происходит за 1 прогон, и необходимо несколько итераций для наполнения кэшей.

Так, решив проблему с импортом, мы стали разбираться дальше. 

А что с бандлами?

Следующим шагом оптимизации времени стала непосредственно сборка бандлов в проекте. 

Мы используем разделение качеств для разных типов девайсов (подробнее о том, как это устроено у нас в проекте, можно почитать здесь). Отсюда следует, что практически весь контент у нас разбит на паки качеств, в которых уже лежат необходимые бандлы с ассетами. 

Мы выявили сразу две проблемы:

  1. Проблема переиспользования бандлов из прошлых сборок;

  2. Проблема однопоточных сборок бандлов.

Первая проблема исходит из того, что мы собираем достаточно большое количество билдов в день: около 60, в дни релиза — еще больше. Как правило, контент в них не сильно меняется: в основном программисты проверяют свою работу либо тестируют новые фичи. Все билды в процессе сборки бандлов выкладывают во внешнее хранилище.

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

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

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

Чтобы этого избежать и снизить время сборки, мы стали действовать по следующей схеме:

  • После импорта проекта мы генерим дополнительные Unity-проекты для каждого необходимого качества, где с помощью symlink линкуем папку Library для каждого сгенерированного проекта. Но будьте аккуратнее: внутри библиотеки есть динамические папки, которые создаются во время сборки. Например, может пересчитываться кэш шейдеров или папка с packages, что может привести к проблемам в параллельных сборках. Соответственно, линковать нужно только статический контент и саму базу. 

  • В отдельных процессах запускаем одновременно несколько экземпляров Unity и в каждом собираем отдельное качество.

  • Результат от каждой сборки мы перемещаем в родительский проект. 

Таким образом, мы смогли распараллелить время сборки бандлов в рамках одной ноды.

Подводя итоги: результаты нашей борьбы с гигантским временем сборки

  • Нам удалось вернуть показатели времени сборок на прежний уровень до выхода War Robots Remastered даже несмотря на то, что количество контента в проекте увеличилось в 2,5 раза. Для этого мы использовали Unity Accelerator. Также удалось убрать зависания импорта, минимизировав вызовы AssetDatabase.Refresh(). 

Нагляднее в цифрах:

На графике ниже можно подробнее посмотреть распределение времени сборки по датам:

  • Мы перестали пересобирать бандлы — вместо этого теперь забираем их сразу готовые из ранее собранных билдов. 

  • Распараллелили сборку бандлов разных качеств, запустив одновременно несколько экземпляров Unity и залинковав ассеты. 

Автор материала — Александр Панов

Теги:
Хабы:
Всего голосов 17: ↑16 и ↓1+20
Комментарии6

Публикации

Информация

Сайт
pixonic.com
Дата регистрации
Дата основания
Численность
201–500 человек
Местоположение
Кипр

Истории