Шаблоны распределения памяти, используемые при разработке игр

20

Я исследовал создание своих собственных методов распределения (которые будут поддерживать такие вещи, как пул памяти и профилирование), однако, продолжая исследование, я искал, как это было сделано в разработке игр.

Какую технику выделения памяти я могу использовать, и почему это хорошая техника?

chadb
источник
1
тебе действительно нужно? это всего лишь одна из самых сложных вещей, которые команда может реализовать когда-либо, если они могут это реализовать.
Ali1S232
4
Это область интересов для меня, поэтому я хотел бы узнать об этом и реализовать ее
chadb
Я должен сказать, что предмет действительно интересный ... Есть случаи, когда это может означать много, но на вашей обычной компьютерной игре я бы скорее беспокоился о реальной игре ...
rioki
Вы исследовали исходный код для современной стандартной библиотеки malloc и free или new и delete? Я спрашиваю, потому что кажется, что это послужило бы очень полезной основой для сравнения любых альтернативных стратегий распределения с алгоритмическим или практическим. Кажется, это также даст реальное представление о том, во что вы будете ввязываться.
Луи Лангхольц

Ответы:

25

Архитектура Game Engine имеет некоторую информацию по этой теме. Основы в том, что вам нужно провести некоторый анализ, чтобы понять, каковы ваши требования к памяти на уровень / кадр / и т. Д. похожи, но есть несколько паттернов, которые автор упоминал несколько раз:

  • Распределители на основе стека. Они выделяют большой сегмент памяти один раз, а затем выделяют указатели в этом блоке памяти в ответ на запросы из других частей игры. Это полезно, чтобы избежать переключения контекста, необходимого для выделения памяти, а также потому, что вы можете использовать свои собственные методы для обеспечения непрерывности или конкретного выравнивания для операций SIMD. Некоторые движки также используют двусторонний стек, где один вид ресурсов загружается сверху, а другой загружается снизу. Возможно, LSR (Load and Stay Resident, то, что понадобится на протяжении всей игры) сверху, и данные для каждого уровня снизу.
  • Память с одним кадром или память с двойным буфером: память для операций, выполняемых в течение одного или двух циклов кадров. Это полезно, потому что вместо того, чтобы выделять / освобождать каждый кадр, вы можете просто удалить данные последнего кадра, сбрасывая указатель, который вы используете для отслеживания памяти в начале блока.
  • Объектные пулы: блок памяти для многих объектов одинакового размера, таких как частицы, враги, снаряды. Они полезны, потому что вы можете легко достичь смежности, найдя первый неиспользованный сегмент в вашем пуле. Они также упрощают итерацию, поскольку каждый объект находится на известном смещении относительно последнего.

Самая большая вещь, на которую автор обращает внимание, это фрагментация памяти. Это не проблема, если вы разрабатываете, например, для ПК, где у вас есть какая-то резервная копия подкачки памяти, на которую вы можете рассчитывать, но в фиксированном контексте памяти, таком как консоль, существует риск «нехватки памяти» при попытке выделить большой объект, потому что ваша память фрагментирована таким образом, что доступны только маленькие непрерывные блоки. С этой целью он рекомендует, чтобы распределитель на основе стека, как указано выше, также включал метод периодической дефрагментации его содержимого.

Для получения дополнительной информации о реальном коде, который в этом задействован, я настоятельно рекомендую статью Кристиана Гирлинга «У нас не хватает памяти?» , которая охватывает методы для пользовательских распределителей, в основном с точки зрения анализа моделей использования памяти, но это также применимо к разработке специального решения для управления памятью.


источник
1

Из того, что я видел (но не сделал), каждая игра имеет тенденцию либо наследовать механизмы распределения от каркаса, от игрового движка, от предыдущей версии (2010 -> 2011), либо получать набор новых, написанных специально для ее структура (либо когда структуры данных могут быть повторно использованы и имеют фиксированный размер или множество типов и переменных размеров).

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

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

Что касается примеров, вы должны просто начать с просмотра игрового движка OGRE 3D, в котором есть несколько вариантов настройки распределителей памяти.

койот
источник
0

Часто допускаемая ошибка заключается в написании собственных распределителей, чтобы вы могли лучше контролировать объем используемой памяти каждой системой и лучше видеть происходящее. Гораздо лучший способ добиться этого - использовать профилировщик памяти. Есть много профилировщиков памяти, мой пример - MemPro . Это абсолютно неинвазивный способ отслеживания всего использования памяти, и вы можете автоматически разбить его на подсистемы, используя фильтры подстановки callstack. В идеале лучше всего разделять выделение памяти и отслеживание памяти, они предъявляют совершенно другие требования.

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

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

Если вам нужен пример замены malloc общего назначения, вы можете взглянуть на мой распределитель VMem . Он был использован во многих поставляемых играх ААА. В нем есть методы, которые ограничивают фрагментацию и сохраняют объем памяти, что очень важно для консольных игр. Это также очень быстро в условиях высокой конкуренции. Мой сайт имеет обширную документацию по этим методам.

Стюарт Линч
источник