Engine & platform

Оптимизация производительности загрузки: Понимание конвейера асинхронной загрузки

JOSEPH SCHEINBERG / UNITY TECHNOLOGIESContributor
Oct 8, 2018|7 Мин
Оптимизация производительности загрузки: Понимание конвейера асинхронной загрузки
Эта веб-страница была переведена с помощью машинного перевода для вашего удобства. Мы не можем гарантировать точность или надежность переведенного контента. Если у вас есть вопросы о точности переведенного контента, обращайтесь к официальной английской версии веб-страницы.

Никто не любит загрузочные экраны. Знаете ли вы, что можно быстро настроить параметры Async Upload Pipeline (AUP), чтобы значительно улучшить время загрузки? В этой статье подробно описано, как сетки и текстуры загружаются через AUP. Это понимание может помочь вам значительно ускорить время загрузки - в некоторых проектах производительность была увеличена более чем в 2 раза!

Читайте далее, чтобы узнать, как AUP работает с технической точки зрения и какие API следует использовать, чтобы получить максимальную отдачу от него.

Попробуйте

Последняя, наиболее оптимальная реализация конвейера загрузки активов доступна в бета-версии 2018.3.

Загрузите 2018.3 Beta сегодня

Для начала давайте подробно рассмотрим, в каких случаях используется AUP и как происходит процесс загрузки.

Когда используется конвейер Async Upload Pipeline?

До версии 2018.3 AUP работал только с текстурами. Начиная с бета-версии 2018.3, AUP теперь загружает текстуры и сетки, но есть и исключения. Текстуры с поддержкой чтения/записи, сетки с поддержкой чтения/записи или сжатые, не будут использовать AUP. (Обратите внимание, что в потоковой передаче мипмапов текстур, которая появилась в версии 2018.2, также используется AUP).

Как происходит процесс погрузки

В процессе сборки объект текстуры или сетки записывается в сериализованный файл, а большие двоичные данные (данные текстуры или вершин) записываются в сопутствующий файл .resS. Эта схема применяется как к данным игрока, так и к пакетам активов. Разделение объектных и двоичных данных позволяет ускорить загрузку сериализованного файла (который обычно содержит небольшие объекты), а также упростить загрузку больших двоичных данных из файла .resS. Когда объект текстуры или сетки десериализован, он отправляет команду в очередь команд AUP. После выполнения этой команды данные текстуры или сетки будут загружены в GPU, и объект можно будет интегрировать в основной поток.

После выполнения этой команды данные текстуры или сетки будут загружены в GPU, и объект можно будет интегрировать в основной поток.

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

Конвейер Async Upload Pipeline имеет следующий процесс для каждой команды:

1. Подождите, пока в кольцевом буфере не освободится необходимая память.

2. Считывание данных из исходного файла .resS в выделенную память.

3. Выполните постобработку (декомпрессия текстур, генерация коллизий сетки, исправление ошибок для каждой платформы и т.д.).

4. Загрузка в потоке рендеринга с разбивкой по времени

5. Освобождение памяти Ring Buffer.

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

Ниже приводится краткое описание этих воздействий:

изображение
Какие общедоступные API доступны для настройки параметров загрузки

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

  • QualitySettings.asyncUploadTimeSlice - Количество времени в миллисекундах, затрачиваемое на загрузку текстур и данных сетки в потоке рендеринга для каждого кадра. Когда выполняется операция асинхронной загрузки, система выполнит два временных среза такого размера. Значение по умолчанию - 2 мс. Если это значение слишком мало, вы можете стать узким местом при загрузке текстур/мешей на GPU. Слишком большое значение, с другой стороны, может привести к задержке частоты кадров.
  • QualitySettings.asyncUploadBufferSize - Размер кольцевого буфера в мегабайтах. Когда временной срез загрузки происходит каждый кадр, мы хотим быть уверены, что у нас достаточно данных в кольцевом буфере, чтобы использовать весь временной срез. Если кольцевой буфер слишком мал, временной отрезок загрузки будет сокращен. В версии 2018.2 по умолчанию было установлено значение 4 МБ, но в версии 2018.3 оно увеличилось до 16 МБ.
  • QualitySettings.asyncUploadPersistentBuffer - Введен в 2018.3, этот флаг определяет, будет ли кольцевой буфер выгрузки деаллоцирован после завершения всех ожидающих чтений. Выделение и удаление этого буфера может часто приводить к фрагментации памяти, поэтому его следует оставить по умолчанию (true). Если вам действительно нужно освобождать память, когда вы не загружаетесь, вы можете установить это значение на false.

Эти настройки можно изменить через API сценариев или через меню QualitySettings.

Изображение Async Upload Persistent Buffer selected
Пример рабочего процесса

Давайте рассмотрим рабочую нагрузку с большим количеством текстур и сеток, загружаемых через конвейер Async Upload Pipeline с использованием стандартного временного интервала 2 мс и кольцевого буфера размером 4 МБ. Поскольку мы загружаемся, мы получаем 2 временных фрагмента на каждый кадр рендеринга, поэтому время загрузки должно составлять 4 миллисекунды. Если посмотреть на данные профилировщика, то мы используем всего около 1,5 миллисекунды. Мы также видим, что сразу после загрузки выполняется новая операция чтения, поскольку память в кольцевом буфере свободна. Это признак того, что необходимо увеличить кольцевой буфер.

Пример чтения файла после загрузки

Давайте попробуем увеличить Ring Buffer и, поскольку мы находимся на экране загрузки, неплохо бы увеличить временной интервал загрузки. Вот как выглядит кольцевой буфер объемом 16 МБ и 4-миллисекундный временной срез:

Изображение того, как выглядит кольцевой буфер объемом 16 МБ и 4-миллисекундный временной срез:

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

Ниже приведено время загрузки примера рабочей нагрузки с различными временными интервалами загрузки и размерами Ring Buffer. Тесты проводились на MacBook Pro, 2,8 ГГц Intel Core i7 под управлением OS X El Capitan. Скорость загрузки и скорость ввода/вывода на разных платформах и устройствах различны. Рабочая нагрузка представляет собой подмножество проекта-образца Viking Village, который мы используем для внутреннего тестирования производительности. Из-за того, что загружаются и другие объекты, мы не можем точно определить выигрыш в производительности при различных значениях. Однако в данном случае можно с уверенностью сказать, что загрузка текстур и сетки происходит как минимум в два раза быстрее при переходе от настроек 4MB/2MS к настройкам 16MB/4MS.

Экспериментирование с этими параметрами дает следующие результаты.

Магия среднего времени загрузки

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

Неизвестный тип блока "codeBlock", укажите для него сериализатор в свойстве `serializers.types`.

Выводы и рекомендации

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

  • Выберите наибольший фрагмент QualitySettings.asyncUploadTimeSlice, который не приводит к выпадению кадров.
  • Во время экранов загрузки временно увеличьте QualitySettings.asyncUploadTimeSlice.
  • Используйте профилировщик для изучения использования временного среза. В профилировщике временной срез будет отображаться как AsyncUploadManager.AsyncResourceUpload. Увеличьте QualitySettings.asyncUploadBufferSize, если ваш временной срез используется не полностью.
  • При большем размере QualitySettings.asyncUploadBufferSize все будет загружаться быстрее, поэтому, если вы можете позволить себе память, увеличьте его до 16 или 32 МБ.
  • Оставьте QualitySettings.asyncUploadPersistentBuffer установленным в true, если у вас нет веских причин для сокращения использования памяти во время выполнения без загрузки.
ЧАСТО ЗАДАВАЕМЫЕ ВОПРОСЫ

Q: Как часто в потоке рендеринга будет происходить загрузка с нарезкой по времени?

  • Загрузка с временными интервалами будет происходить один раз за кадр рендеринга или дважды во время асинхронной загрузки. VSync влияет на этот конвейер. Пока поток рендеринга ждет VSync, вы можете заниматься загрузкой. Если вы работаете с частотой кадров 16 мс, а затем один кадр затягивается, скажем, до 17 мс, то в итоге вы будете ждать vsync 15 мс. В целом, чем выше частота кадров, тем чаще будут происходить временные срезы загрузки.

Q: Что загружается через AUP?

  • Текстуры, не поддерживающие чтение/запись, загружаются через AUP.
  • Начиная с версии 2018.2, текстурные мипмапы передаются через AUP.
  • Начиная с версии 2018.3, сетки также можно загружать через AUP, если они не сжаты и не включены в режим чтения/записи.

Q: Что делать, если кольцевой буфер недостаточно велик для хранения загружаемых данных (например, очень большой текстуры)?

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

Q: Как работают API синхронной загрузки? Например, Resources.Load, AssetBundle.LoadAsset и т. д.

  • Синхронные вызовы загрузки используют AUP и, по сути, блокируют основной поток до тех пор, пока не завершится операция асинхронной загрузки. Тип используемого API для загрузки не имеет значения.
Расскажите нам, что вы думаете.

Мы всегда рады обратной связи. Сообщите нам о своем мнении в комментариях или на форуме бета-версии Unity 2018.3!