Профилирование памяти в Unity
Профилируя и оттачивая характеристики своей игры для широкого спектра платформ и устройств, вы сможете расширить базу игроков и повысить свои шансы на успех.
На этой странице представлена информация о двух инструментах для анализа использования памяти в вашем приложении в Unity: встроенный модуль Memory Profiler и пакет Memory Profiler- пакет Unity, который вы можете добавить в свой проект.
Приведенная здесь информация является выдержкой из электронной книги " Ultimate guide to profiling Unity games ", которую можно скачать бесплатно. Электронная книга была создана как внешними, так и внутренними экспертами Unity в области разработки, профилирования и оптимизации игр.
Читайте дальше, чтобы узнать о профилировании памяти в Unity.
Профилирование памяти полезно для тестирования на ограничения памяти аппаратной платформы, уменьшения времени загрузки и количества сбоев, а также для обеспечения совместимости проекта с устаревшими устройствами. Это также может быть актуально, если вы хотите повысить производительность CPU/GPU, внеся изменения, которые фактически увеличивают использование памяти. Это в значительной степени не связано с производительностью во время выполнения.
Существует два способа анализа использования памяти в вашем приложении в Unity.
Модуль Memory Profiler: Это встроенный модуль профилировщика, который предоставляет вам основную информацию о том, как ваше приложение использует память.
Пакет профайлера памяти Это пакет Unity, который вы можете добавить в свой проект. Она добавляет в Unity Editor дополнительное окно Memory Profiler, с помощью которого вы можете еще более детально проанализировать использование памяти в вашем приложении. Вы можете сохранять и сравнивать отдельные графики для поиска утечек памяти, а также просматривать структуру памяти для выявления проблем с ее фрагментацией.
С помощью этих встроенных инструментов вы можете отслеживать использование памяти, находить области приложения, где объем памяти превышает ожидаемый, а также находить и улучшать фрагментацию памяти.
Понимание и учет ограничений памяти целевых устройств очень важны для многоплатформенной разработки. При разработке сцен и уровней придерживайтесь бюджета памяти, установленного для каждого целевого устройства. Установив ограничения и рекомендации, вы можете гарантировать, что ваше приложение будет работать в рамках аппаратных спецификаций каждой платформы.
Спецификации памяти устройства можно найти в документации разработчика. Например, консоль Xbox One, согласно документации, ограничена 5 ГБ максимально доступной памяти для игр, запущенных на переднем плане.
Это также может быть полезно для установления лимитов контента на сложность сетки и шейдеров, а также для сжатия текстур. Все это влияет на объем выделяемой памяти. К этим цифрам бюджета можно обращаться в течение всего цикла разработки проекта.
Определите границы физической оперативной памяти
Каждая целевая платформа имеет свой лимит памяти, и, узнав его, вы сможете установить бюджет памяти для своего приложения. Используйте Memory Profiler, чтобы просмотреть снимок захвата. В разделе Hardware Resources (см. изображение выше) указаны размеры физической памяти с произвольным доступом (RAM) и видеопамяти с произвольным доступом (VRAM). Эта цифра не учитывает того факта, что не все это пространство может быть доступно для использования. Тем не менее, это полезная цифра для начала работы.
Нелишним будет ознакомиться со спецификациями аппаратного обеспечения для целевых платформ, поскольку приведенные здесь цифры не всегда отражают полную картину. Аппаратные средства из комплекта разработчика иногда имеют больший объем памяти, или вы можете работать с аппаратными средствами, имеющими унифицированную архитектуру памяти.
Определите аппаратное обеспечение с наименьшими характеристиками по объему оперативной памяти для каждой поддерживаемой платформы и используйте его для принятия решения о бюджете памяти. Помните, что не вся физическая память может быть доступна для использования. Например, на консоли может быть запущен гипервизор для поддержки старых игр, который может использовать часть общей памяти. Подумайте, какой процент (например, 80% от общей суммы) можно использовать. Для мобильных платформ можно также рассмотреть возможность разделения на несколько уровней спецификаций, чтобы обеспечить лучшее качество и возможности для пользователей устройств более высокого класса.
После того как вы определили бюджет памяти, подумайте о том, чтобы установить бюджеты памяти для каждой команды. Например, художники по окружению получают определенный объем памяти для каждого загружаемого уровня или сцены, звуковая команда выделяет память для музыки и звуковых эффектов, и так далее.
Важно гибко подходить к бюджету по мере реализации проекта. Если одна из команд уложилась в бюджет, передайте излишки другой, если она сможет улучшить разрабатываемую игру.
После того, как вы определились и установили бюджеты памяти для целевых платформ, следующим шагом будет использование инструментов профилирования, которые помогут вам контролировать и отслеживать использование памяти в вашей игре.
Модуль Memory Profiler обеспечивает два вида просмотра: Просто и подробно. Используйте представление Simple, чтобы получить высокоуровневое представление об использовании памяти вашим приложением. При необходимости переключитесь на детальное представление, чтобы углубиться в детали.
Простой
Показатель Total Reserved Memory - это "Total Tracked by Unity Memory". Она включает в себя память, которую Unity зарезервировала, но не использует в данный момент (эта цифра - Total Used Memory).
Показатель "Используемая память системы" - это то, что ОС считает используемым вашим приложением. Если этот показатель когда-либо отображает 0, имейте в виду, что счетчик Profiler не реализован на профилируемой платформе. В этом случае лучше всего опираться на показатель Total Reserved Memory. Для получения подробной информации о памяти в таких случаях также рекомендуется перейти на инструмент профилирования родной платформы.
Чтобы узнать, сколько памяти используется вашим исполняемым файлом, библиотеками DLL и виртуальной машиной Mono, цифры памяти за кадром не подойдут. Чтобы разобраться с подобными проблемами в памяти, воспользуйтесь детальным снимком.
Примечание: Дерево ссылок в детальном представлении модуля Memory Profiler показывает только нативные ссылки. Ссылки на объекты типов, наследующих UnityEngine.Object, могут отображаться с именем их управляемых оболочек. Однако они могут отображаться только потому, что под ними находятся Native Objects. Вы не обязательно увидите какой-либо управляемый тип. В качестве примера возьмем объект, в одном из полей которого содержится ссылка на Texture2D. Используя это представление, вы также не увидите, на какое поле содержится ссылка. Для такой детализации используйте пакет Memory Profiler.
Чтобы определить на высоком уровне, когда использование памяти начинает приближаться к бюджету платформы, воспользуйтесь следующим расчетом "с обратной стороны салфетки":
Используемая системой память (или общая зарезервированная память, если "Используемая системой" равна 0) + буфер неотслеживаемой памяти / общая память платформы
Когда эта цифра начнет приближаться к 100% бюджета памяти вашей платформы, воспользуйтесь пакетом Memory Profiler, чтобы выяснить причину.
Многие функции модуля Memory Profiler были заменены пакетом Memory Profiler, но вы все еще можете использовать этот модуль для дополнения своих усилий по анализу памяти.
Например:
- Чтобы заметить распределение GC: Хотя они отображаются в модуле, их легче отследить с помощью Project Auditor или Deep Profiling.
- Чтобы быстро посмотреть размер используемой/зарезервированной кучи
- Анализ шейдерной памяти
При определении бюджета памяти не забывайте ориентироваться на устройство с наименьшими характеристиками для вашей целевой платформы. Внимательно следите за использованием памяти, не забывая о целевых ограничениях.
Как правило, для профилирования используется мощная система разработчика с большим объемом памяти (важно место для хранения больших снимков памяти или быстрой загрузки и сохранения этих снимков).
Профилирование памяти - это другой зверь по сравнению с профилированием CPU и GPU, поскольку оно само по себе может нести дополнительные расходы на память. Вам может понадобиться профилировать память на устройствах более высокого класса (с большим объемом памяти), но при этом необходимо следить за ограничением бюджета памяти для целевой спецификации более низкого класса.
При профилировании использования памяти необходимо учитывать некоторые моменты:
- Такие параметры, как уровни качества, графические уровни и варианты AssetBundle, могут занимать разное количество памяти на более мощных устройствах. Например:
- Настройки Quality Level и Graphics могут влиять на размер RenderTextures, используемых для карт теней.
- Масштабирование разрешения может повлиять на размер экранных буферов, RenderTextures и эффектов постобработки.
- Настройка качества текстур может повлиять на размер всех текстур.
- Максимальный LOD может повлиять на модели и многое другое.
- Если у вас есть варианты AssetBundle, такие как HD (High Definition) и SD (Standard Definition), и вы выбираете, какой из них использовать, исходя из спецификаций устройства, вы также можете получить разные размеры активов в зависимости от того, на каком устройстве вы проводите профилирование.
- Разрешение экрана вашего целевого устройства влияет на размер RenderTextures, используемых для эффектов постобработки.
- Поддерживаемый графический API устройства может влиять на размер шейдеров в зависимости от того, какие варианты шейдеров поддерживаются или не поддерживаются API.
- Многоуровневая система, использующая различные настройки качества, графические уровни и варианты пакетов активов, - это отличный способ ориентироваться на более широкий спектр устройств, например, загружая версию пакета активов в высоком разрешении на мобильное устройство с 4 Гб памяти, а версию в стандартном разрешении - на устройство с 2 Гб памяти. Однако не забывайте о вышеуказанных различиях в использовании памяти и обязательно тестируйте оба типа устройств, а также устройства с разными разрешениями экрана или поддерживаемыми графическими API.
Примечание: Редактор Unity, как правило, всегда показывает больший объем памяти из-за дополнительных объектов, загружаемых из редактора и профилировщика. Он может даже показать память ассетов, которые не будут загружены в память при сборке, например, из пакетов ассетов (в зависимости от режима моделирования Addressables) или спрайтов и атласов, или для ассетов, показанных в инспекторе. Некоторые из цепочек ссылок также могут быть более запутанными в редакторе.
В настоящее время Memory Profiler находится в предварительной версии для Unity 2019 LTS или более новых версий, но ожидается, что он будет проверен в Unity 2022 LTS.
Одним из преимуществ пакета Memory Profiler является то, что помимо захвата собственных объектов (как это делает модуль Memory Profiler), он также позволяет просматривать управляемую память, сохранять и сравнивать снимки, а также изучать содержимое памяти еще более подробно, с визуальным разбиением использования памяти.
Снимок показывает распределение памяти в движке, позволяя быстро определить причины чрезмерного или ненужного использования памяти, отследить утечки памяти или увидеть фрагментацию кучи.
После установки пакета Memory Profiler откройте его, нажав Window > Analysis > Memory Profiler.
В верхней строке меню Memory Profiler можно изменить цель выбора плеера, а также захватить или импортировать снимки.
Примечание: Профилируйте память на целевом оборудовании, подключив Memory Profiler к удаленному устройству с помощью выпадающего списка Target selection. Профилирование в редакторе Unity даст вам неточные цифры из-за накладных расходов, добавляемых редактором и другими инструментами.
Слева от окна Memory Profiler находится область Workbench. С его помощью можно управлять сохраненными снимками памяти, открывать или закрывать их. С помощью этой области можно также переключаться между представлениями "Одиночные" и "Сравнение снимков".
Подобно Profile Analyzer, Memory Profiler позволяет загрузить два набора данных (снимки памяти) для их сравнения. Это особенно полезно при изучении роста использования памяти с течением времени или между сценами, а также при поиске утечек памяти.
В главном окне Memory Profiler есть несколько вкладок, позволяющих изучить снимки памяти, в том числе "Сводка", "Объекты и выделения" и "Фрагментация". Давайте рассмотрим каждый из этих вариантов подробнее.
Выбирайте этот вид, когда хотите получить быстрый обзор использования памяти в проекте. В нем также содержатся полезные и важные показатели, связанные с памятью, для рассматриваемого снимка памяти. Это идеальное решение для быстрого просмотра того, что происходит в тот момент, когда был сделан снимок.
Представление Tree Map отображает разбивку памяти, используемой объектами, в виде графической карты Tree Map, в которую можно заглянуть, чтобы определить тип объектов, потребляющих больше всего памяти.
Под представлением "Древовидная карта" находится таблица с фильтрацией, которая обновляется для отображения списка объектов в выбранных ячейках сетки.
Карта дерева показывает память, приписанную объектам, как родным, так и управляемым. Память управляемых объектов, как правило, меньше памяти нативных объектов, что затрудняет ее обнаружение в представлении карты. Вы можете увеличить масштаб карты дерева, чтобы рассмотреть их, но для осмотра небольших объектов таблицы обычно дают лучший обзор. Нажав на ячейки в древовидной карте, можно отфильтровать таблицу под ней по типу раздела и/или выбрать интересующий объект в таблице.
Вы можете отследить, какие элементы ссылаются на объекты в этом списке и, возможно, в каких полях класса Managed находятся эти ссылки, выбрав строку таблицы или ячейку сетки Tree Map, которая ее представляет, а затем отметив раздел References на боковой панели Details. Если сторона скрыта, вы можете сделать ее видимой с помощью кнопки переключения в правой верхней части панели инструментов окна.
Примечание: Карта дерева показывает только объекты в памяти. Это не полное представление отслеживаемой памяти. Это важно понимать, если вы заметили, что цифры в обзоре использования памяти не совпадают с суммой отслеживаемой памяти.
Это происходит из-за того, что не вся родная память привязана к Objects. Он также может состоять из не связанных с объектами нативных распределений, таких как исполняемые файлы и библиотеки DLL, нативные массивы и т. д. Даже более абстрактные понятия, такие как "зарезервированное, но неиспользуемое пространство памяти", могут играть роль в общем объеме Native Allocations.
В представлении "Объекты и выделения" отображается таблица, которую можно переключить для фильтрации на основе готовых отборов, таких как "Все объекты", "Все родные объекты", "Все управляемые объекты", "Все родные выделения" и т. д.
Вы можете переключить нижнюю таблицу для отображения объектов, распределений или областей памяти в выбранном диапазоне. Как уже отмечалось для представления Tree Map, не вся память связана с объектами, поэтому страницы All Memory Regions и All Native Allocations могут дать более полное представление об использовании памяти, где Memory Regions также включает зарезервированную, но неиспользуемую в данный момент память.
Используйте это в своих интересах, когда оптимизируете использование памяти и стремитесь к более эффективному использованию памяти для аппаратных платформ, где бюджеты на память ограничены.
Загрузите снимок Memory Profiler и перейдите к просмотру карты Tree Map, чтобы просмотреть категории, упорядоченные от наибольшего к наименьшему размеру отпечатков памяти.
Активы проекта часто являются самыми высокими потребителями памяти. Используя представление таблицы, найдите объекты текстур, мешей, аудиоклипов, текстур рендеринга, шейдеров и предварительно распределенных буферов. Все они являются хорошими кандидатами на оптимизацию памяти.
Утечка памяти обычно происходит, когда:
- Объект не освобождается из памяти вручную с помощью кода
- Объект остается в памяти из-за непреднамеренной ссылки
Режим Memory Profiler Compare помогает найти утечки памяти, сравнивая два снимка за определенный промежуток времени.
Распространенный сценарий утечки памяти в играх Unity может возникнуть после выгрузки сцены.
В пакете Memory Profiler есть рабочий процесс, который поможет вам обнаружить эти типы утечек с помощью режима Compare.
Благодаря дифференциальному сравнению нескольких снимков памяти можно определить источник непрерывного выделения памяти в течение жизни приложения.
В следующих разделах приведены некоторые советы, которые помогут определить управляемые распределения кучи в ваших проектах.
Модуль Memory Profiler в Unity Profiler отображает управляемые выделения на кадр красной линией. Большую часть времени этот показатель должен быть равен 0, поэтому любые скачки в этой линии указывают на фреймы, которые следует исследовать на предмет управляемых распределений.
В представлении Timeline в модуле CPU Usage Profiler выделения, в том числе управляемые, выделены розовым цветом, что позволяет легко увидеть их и сосредоточиться на них.
Стеки вызовов выделений позволяют быстро обнаружить управляемые выделения памяти в вашем коде. Они обеспечат необходимую детализацию стека вызовов при меньших накладных расходах, чем при глубоком профилировании, и могут быть включены на лету с помощью стандартного профилировщика.
По умолчанию в Profiler отключены стеки вызовов выделений. Чтобы включить их, нажмите кнопку Call Stacks на главной панели инструментов окна Profiler. Измените вид "Подробности" на "Связанные данные".
Примечание: Если вы используете старую версию Unity (до появления поддержки стека вызовов Allocation), то глубокое профилирование - это хороший способ получить полный стек вызовов, чтобы помочь найти управляемые выделения.
Образцы GC.Alloc, выбранные в иерархии или Raw Hierarchy, теперь будут содержать свои стеки вызовов. Вы также можете увидеть стеки вызовов примеров GC.Alloc во всплывающей подсказке выбора на Timeline.
В представлении "Иерархия" профилировщика использования ЦП можно щелкнуть по заголовкам столбцов, чтобы использовать их в качестве критериев сортировки. Сортировка по GC Alloc - отличный способ сосредоточиться на них.
Project Auditor - это экспериментальный инструмент статического анализа. Он делает много полезных вещей, некоторые из которых выходят за рамки этого руководства, но он может выдать список всех строк кода в проекте, которые вызывают управляемое выделение, без необходимости запускать проект. Это очень эффективный способ поиска и расследования подобных проблем.
Unity использует сборщик мусора Boehm-Demers-Weiser, который останавливает выполнение кода вашей программы и возобновляет нормальное выполнение только после завершения своей работы.
Помните о ненужных выделениях из кучи, которые могут вызвать скачки GC.
- Струны: В C# строки являются ссылочными типами, а не типами значений. Это означает, что каждая новая строка будет выделена в управляемой куче, даже если она используется только временно. Сократите ненужное создание и манипулирование строками. Избегайте разбора строковых файлов данных, таких как JSON и XML, и храните данные в ScriptableObjects или форматах MessagePack или Protobuf. Используйте класс StringBuilder, если вам нужно создавать строки во время выполнения программы.
- Вызовы функций единства: Некоторые функции Unity API создают выделения в куче, особенно те, которые возвращают массив управляемых объектов. Кэшируйте ссылки на массивы, а не выделяйте их в середине цикла. Кроме того, воспользуйтесь некоторыми функциями, которые позволяют не генерировать мусор. Например, используйте GameObject.CompareTag вместо того, чтобы вручную сравнивать строку с GameObject.tag (так как возврат новой строки создает мусор).
- Бокс: Избегайте передачи переменной с типом значения вместо переменной с типом ссылки. При этом создается временный объект, а потенциальный мусор, который появляется вместе с ним, неявно преобразует тип значения в тип объекта (например, int i = 123; object o = i). Вместо этого постарайтесь предоставить конкретные переопределения с типом значения, которое вы хотите передать. Для этих переопределений также можно использовать генерики.
- Корутины: Хотя yield не производит мусор, создание нового объекта WaitForSeconds производит. Кэшируйте и повторно используйте объект WaitForSeconds вместо того, чтобы создавать его в строке yield или использовать yield return null.
- LINQ и регулярные выражения: И в том, и в другом случае мусор генерируется закулисным боксом. Избегайте LINQ и регулярных выражений, если для вас важна производительность. Пишите циклы for и используйте списки в качестве альтернативы созданию новых массивов.
- Generic Collections и другие управляемые типы: Не объявляйте и не заполняйте список или коллекцию каждый кадр в Update (например, список врагов в определенном радиусе от игрока). Вместо этого сделайте список членом MonoBehaviour и инициализируйте его в Start. Просто опустошите коллекцию с помощью Clear перед использованием каждого кадра.
Временная сборка мусора, когда это возможно
Если вы уверены, что остановка сборки мусора не повлияет на определенный момент игры, вы можете запустить сборку мусора с помощью System.GC.Collect.
Примеры использования этой возможности в своих интересах см. в разделе " Понимание автоматического управления памятью ".
Используйте инкрементный сборщик мусора, чтобы разделить нагрузку на GC.
Вместо того чтобы создавать одно длинное прерывание во время выполнения программы, инкрементная сборка мусора использует несколько коротких прерываний, которые распределяют рабочую нагрузку на множество кадров. Если сборка мусора вызывает неравномерную частоту кадров, попробуйте использовать эту опцию, чтобы узнать, может ли она уменьшить проблему скачков GC. Используйте Profile Analyzer, чтобы убедиться в его преимуществах для вашего приложения.
Обратите внимание, что использование GC в режиме Incremental добавляет барьеры чтения-записи в некоторые вызовы C#, что влечет за собой некоторые накладные расходы, которые могут добавить до ~1 мс на кадр накладных расходов на вызов сценария. Для оптимальной производительности идеально не иметь GC Allocs в основных игровых циклах, чтобы не нуждаться в Incremental GC для плавной частоты кадров и спрятать GC.Collect там, где пользователь его не заметит, например, при открытии меню или загрузке нового уровня.
Чтобы узнать больше о Memory Profiler, ознакомьтесь со следующими ресурсами:
- Документация по Memory Profiler
- Улучшение использования памяти с помощью профилировщика памяти в Unity
- Memory Profiler Инструмент для устранения проблем, связанных с памятью Сеанс Unite
- Работа с профилировщиком памяти Unity Learn session
Скачать электронную книгу Полное руководство по профилированию игр Unityбесплатно, чтобы получить все советы и лучшие практики.