Разработка игр
Memory Management в играх
Среднее мобильное устройство имеет 4GB RAM, из которых игре достаётся 1.5-2GB. PS4 даёт 5GB. Switch - 3GB. Каждый мегабайт - это решение: эта текстура или та анимация? Игры, которые crash'ятся с OOM, получают 1-звёздочные отзывы. GC stutter в VR вызывает физический дискомфорт. Memory management - это профессия.
- **Red Dead Redemption 2** - 150GB+ ассетов, работающих на 8GB PS4: aggressive streaming, 4 уровня текстурных mip'ов, динамическое управление бюджетом в зависимости от биома
- **Pokémon GO** переработал memory management после волны OOM crashes на Android 2016: переход на Object Pooling для AR объектов снизил heap allocation на 70%
- **Beat Saber** (VR) требует 0ms GC паузы: написан с explicit zero-allocation hot path, все ноты из предаллоцированного пула, GC запускается только в меню
- **Genshin Impact** поддерживает iOS 9.0+ с 2GB RAM: адаптивные texture streaming budgets в зависимости от доступной RAM устройства, определяемой при старте
Pool Allocator и кастомные аллокаторы
**Системный malloc - враг производительности игр.** Частые malloc/free фрагментируют heap, замедляют аллокацию и создают непредсказуемые паузы. Pool Allocator решает это: заранее выделяет большой блок памяти и раздаёт фиксированные куски одного размера. Нет фрагментации, нет обращений к OS, аллокация за O(1).
Unreal Engine использует TPooledLinkedList и TCircularBuffer для hot-path аллокаций. Unity DOTS (Data-Oriented Technology Stack) использует NativeArray и Allocator.TempJob - аллокации в custom allocator без GC. Все аллокации в performance-critical code должны идти через пулы, а не через new/malloc.
Pool Allocator выделяет блоки фиксированного размера sizeof(T). Нужно хранить объекты трёх разных типов (Enemy, Bullet, Particle). Как организовать пулы?
Asset Streaming
**Asset streaming** - загрузка ресурсов по мере необходимости, а не всё сразу. GTA V map весит 65GB - закономерно, не помещается в RAM. World of Warcraft загружает чанки мира вокруг персонажа. Современные игры предсказывают движение камеры и загружают ресурсы заранее, чтобы не было видимых задержек.
Texture Streaming - отдельная система в Unreal и Unity: текстуры загружаются в самом низком mip level, затем постепенно загружаются более детальные mip'ы по мере приближения. Unreal Engine Texture Streaming Pool (r.Streaming.PoolSize) определяет бюджет памяти для текстур. Превышение бюджета = degraded mip levels = blur на объектах.
Игрок движется по открытому миру. Streaming system загружает чанки по мере движения. Иногда появляются hitches (паузы 100-300ms) при загрузке нового чанка. Как устранить?
Избегание GC в Unity/C#
**Garbage Collector в Unity** - источник stuttering. GC срабатывает, когда heap переполняется, и может заморозить игру на 1-5ms. В VR это катастрофа (frame drop с 90fps ощущается как физическая тошнота). Принцип: не создавать managed heap allocations в hot code path (Update, FixedUpdate, OnCollisionEnter).
Unity Memory Profiler (Package Manager) показывает все managed allocations с call stack. Ищи повторяющиеся аллокации одного и того же типа в одном месте. Unity 2021+ поддерживает Incremental GC - GC работает по чуть-чуть каждый кадр вместо большой паузы. Включить: Project Settings > Player > Use incremental GC.
string.Format("Score: {0}", score) вызывается каждый кадр в Update(). Это проблема?
Memory Budgets по платформам
**Игра без бюджета памяти - игра, которую убьёт OS.** На iOS 3GB RAM App получает kill при превышении ~1.5GB. На PS4 (8GB) - ОС берёт 3GB, игре остаётся 5GB. На Switch (4GB) - ещё жёстче. Memory budget определяет распределение: сколько на текстуры, на геометрию, на аудио, на код и runtime данные.
Addressables Reference Counting в Unity: каждый LoadAssetAsync увеличивает ref count. Когда ref count падает до 0 через Release() - ассет выгружается из памяти. Утечки памяти в Unity часто происходят из-за незакрытых Addressable handles - ассет загружен, объект уничтожен, но Release не вызван.
64-битные платформы имеют 'бесконечную' виртуальную память - OOM невозможен
OOM killing происходит по физической RAM, а не виртуальной памяти. iOS убивает процессы превысившие физический лимит, Android тоже через OOM killer
Виртуальная память позволяет адресовать больше, чем доступно физически, но backing store (RAM или swap) ограничен. На мобильных iOS нет swap - превышение физического лимита = немедленный kill без предупреждения
iOS приложение крашится на iPhone 12 (4GB RAM) с OOM error. Memory Profiler показывает 1.8GB в texture streaming pool. Бюджет был 500MB. В чём ошибка?
Ключевые идеи
- **Pool Allocator:** O(1) аллокация, нет фрагментации, нет обращений к OS; отдельный пул для каждого типа объекта
- **Asset Streaming:** Addressables с ref counting; predictive streaming загружает ассеты до прихода игрока; Texture Streaming Pool - отдельный бюджет
- **GC Avoidance:** нет heap allocations в Update(); StringBuilder вместо string concat; pre-allocated буферы; Incremental GC как safety net
- **Memory Budgets:** распределить явно по категориям; мобильный OOM = kill без предупреждения; Release() каждый LoadAssetAsync
Связанные темы
Управление памятью затрагивает все аспекты игрового движка:
- Производительность: GPU и CPU — Static Batching дублирует mesh данные в памяти - trade-off между CPU batching performance и memory usage
- Архитектура AAA движка — Custom allocators, virtual memory management и asset streaming - фундаментальные системы любого AAA движка
- Game Pipeline и CI/CD — Memory budget tracking через автоматизированные тесты в CI: build превысил texture budget - провал проверки
Вопросы для размышления
- Pool Allocator эффективен для объектов одного размера. Как организовать memory management для объектов с непредсказуемым временем жизни и разными размерами - например, JSON payload'ы от сервера? Какие паттерны используют игровые движки?
- Unity Incremental GC разбивает GC pause на маленькие куски (< 0.5ms за кадр). Почему это не полное решение проблемы - когда Incremental GC всё равно вызывает stutter?
- SSD на PS5 (5.5 GB/s) быстрее RAM на PS3 (25 GB/s на PS3 XDR RDRAM - нет, RAM быстрее). Как Sony использует эту скорость SSD для изменения архитектуры streaming - что стало возможным, чего не было раньше?