Как вы готовитесь к нехватке памяти?

18

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

Возможные техники:

  • Используйте пулы памяти с верхним пределом.
  • Периодически удаляйте ненужные объекты.
  • Выделите дополнительный объем памяти в начале, чтобы потом ее можно было освободить как механизм восстановления. Я бы сказал, около 2-4 МБ.

Скорее всего, это произойдет на мобильных / консольных платформах, где память обычно ограничена в отличие от вашего 16 ГБ ПК. Я предполагаю, что у вас есть полный контроль над распределением / освобождением памяти и не участвует сбор мусора. Вот почему я отмечаю это как C ++.

Обратите внимание, что я не говорю об эффективном пункте C ++ 7 «Будьте готовы к условиям нехватки памяти» , хотя это и уместно, я хотел бы получить ответ, более связанный с разработкой игры, где вы обычно имеете больший контроль над тем, что происходит.

Резюмируя вопрос, как вы готовитесь к нехватке памяти для игр-песочниц, когда вы ориентируетесь на платформу с ограниченной памятью консоль / мобильный?

concept3d
источник
Сбой выделения памяти довольно редко встречается в современных операционных системах ПК, потому что они автоматически переключаются на жесткий диск при нехватке физической памяти. Тем не менее, следует избегать ситуации, поскольку перестановка происходит намного медленнее, чем физическая оперативная память, и сильно влияет на производительность.
Филипп
@ Филипп да, я знаю. Но мой вопрос больше относится к устройствам с ограниченной памятью, таким как консоли и мобильные телефоны, я думаю, что я упоминал об этом
concept3d
Это довольно широкий вопрос (и такой опрос, как он сформулирован). Можете ли вы немного сузить сферу, чтобы быть более конкретным для отдельной ситуации?
MichaelHouse
@ Byte56 Я отредактировал вопрос. Я надеюсь, что у этого есть более определенная область теперь.
concept3d

Ответы:

16

Как правило, вы не справляетесь с нехваткой памяти. Единственный разумный вариант в программном обеспечении, таком большом и сложном, как игра, - просто сбить / утвердить / завершить работу распределителя памяти как можно скорее (особенно в отладочных сборках). Условия нехватки памяти тестируются и обрабатываются в некоторых основных системных или серверных программах в некоторых случаях, но обычно в других местах.

Когда у вас есть верхний предел памяти, вы просто гарантируете, что вам никогда не понадобится больше, чем этот объем памяти. Например, вы можете сохранить максимальное количество разрешенных NPC за один раз и просто прекратить порождать новых несущественных NPC, как только эта крышка будет достигнута. Для важных NPC вы можете либо заменить их второстепенными, либо иметь отдельный пул / колпачок для важных NPC, которые ваши дизайнеры знают для разработки (например, если у вас есть только 3 основных NPC, дизайнеры не будут помещать более 3 в область / кусок - хорошие инструменты помогут дизайнерам сделать это правильно, и тестирование, конечно, необходимо).

Действительно хорошая потоковая система также важна, особенно для игр с песочницей. Вам не нужно хранить все NPC и предметы в памяти. По мере того, как вы перемещаетесь по кусочкам мира, новые куски будут поступать внутрь, а старые куски вытекать. Как правило, они включают в себя NPC и предметы, а также ландшафт. Принимая во внимание эту систему, необходимо установить ограничения на дизайн и конструирование лимитов предметов, зная, что не более X старых чанков будут храниться и активно загружаться Y новых чанков будут загружаться, поэтому в игре должно быть место, чтобы сохранить все данные X + Y + 1 кусков в памяти.

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

allocate(bytes):
  if can_allocate(bytes):
    return internal_allocate(bytes)
  else:
    warning(LOW_MEMORY)
    tell_systems_to_dump_caches()

    if can_allocate(bytes):
      return internal_allocate(bytes)
    else:
      fatal_error(OUT_OF_MEMORY)

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

Вы можете также рассмотреть возможность выгрузки копий некоторых данных в высоком разрешении, например, вы можете сбросить текстуру уровней mipmap с более высоким разрешением, если вам не хватает памяти GPU (или любой памяти в архитектуре с разделяемой памятью). Это обычно требует много архитектурных работ, чтобы это того стоило.

Обратите внимание, что некоторые очень неограниченные игры-песочницы могут быть просто легко разбиты, даже на ПК (помните, что обычные 32-битные приложения имеют ограничение в 2-3 ГБ адресного пространства, даже если у вас есть компьютер с 128 ГБ ОЗУ; Битовая ОС и аппаратное обеспечение позволяют одновременно запускать больше 32-разрядных приложений, но ничего не могут сделать, чтобы 32-разрядный двоичный файл имел большее адресное пространство). В конце концов, у вас либо очень гибкий игровой мир, для которого в каждом случае потребуется неограниченное пространство памяти, либо у вас очень ограниченный и контролируемый мир, который всегда отлично работает в ограниченной памяти (или что-то среднее).

Шон Миддледич
источник
+1 за этот ответ. Я написал две системы, которые работают с использованием стиля Шона и пулов дискретной памяти, и обе они хорошо работали на производстве. Первым был спавнер, который откатывал вывод на кривой до максимального предельного отключения, чтобы игрок никогда не заметил внезапного снижения (хотя общая пропускная способность была снижена этим запасом безопасности). Второе было связано с кусками в том смысле, что неудачное распределение вызвало бы чистку и перераспределение. Я чувствую, что ** очень ограниченный и контролируемый мир, который всегда отлично работает в ограниченной памяти **, жизненно важен для любого долго работающего клиента.
Патрик Хьюз
+1 за упоминание о своей агрессивности с обработкой ошибок в сборках отладки, насколько это возможно. Помните, что на оборудовании для отладки консоли вы иногда имеете доступ к большему количеству ресурсов, чем в розницу. Возможно, вы захотите имитировать эти условия на оборудовании dev, выделяя объекты отладки исключительно в адресном пространстве, превышающем то, что было бы у розничных устройств, и вызывая сбой при использовании эквивалентного розничного пространства.
FlintZA
5

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

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

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

Raxvan
источник
1

Что ж, вы можете выделить около 16 МБ (просто чтобы быть уверенным на 100%) при запуске или даже во .bssвремя компиляции и использовать «безопасный распределитель» с такой сигнатурой inline __attribute__((force_inline)) void* alloc(size_t size)( __attribute__((force_inline))это GCC / mingw-w64атрибут, который вызывает встраивание критических участков кода) даже если оптимизации отключены, даже если они должны быть включены для игр) вместо того, mallocчтобы пытаться, void* result = malloc(size)и если это не удается, отбросьте кеш, освободите свободную память (или скажите другому коду использовать эту .bssвещь, но это выходит за рамки этого ответа) и сбросить несохраненные данные (сохранить мир на диск, если вы используете концепцию чанков в стиле Minecraft, назовите что-нибудь подобное saveAllModifiedChunks()). Тогда, еслиmalloc(16777216) (выделение этих 16 МиБ снова) не удается (снова замените на аналог для .bss), завершите игру и покажитеMessageBox(NULL, "*game name* couldn't continue because of lack of free memory, but your world was safely saved. Try closing background applications and restarting the game", "*Game name*: out of memory", MB_ICONERROR)или альтернатива конкретной платформы. Собираем все вместе:

__attribute__((force_inline)) void* alloc(size_t size) {
    void* result = malloc(size); // Attempt to allocate normally
    if (!result) { // If the allocation failed...
        if (!reserveMemory) std::_Exit(); // If alloc() was called from forceFullSave() or reportOutOfMemory() and we again can't allocate, just quit, something is stealing all our memory. If we used the .bss approach, this wouldn't've been necessary.
        free(reserveMemory); // Global variable, pointer to the reserve 16 MiB allocated on startup
        forceFullSave(); // Saves the game
        reportOutOfMemory(); // Platform specific error message box code
        std::_Exit(); // Close silently
    } else return result;
}

Вы можете использовать аналогичное решение, std::set_new_handler(myHandler)где myHandlerвызывается void myHandler(void), когда происходит newсбой:

void newerrhandler() {
    if (!reserveMemory) std::_Exit(); // If new was called from forceFullSave() or reportOutOfMemory() and we again can't allocate, just quit, something is stealing all our memory. If we used the .bss approach, this wouldn't've been necessary.
    free(reserveMemory); // Global variable, pointer to the reserve 16 MiB allocated on startup
    forceFullSave(); // Saves the game
    reportOutOfMemory(); // Platform specific error message box code
    std::_Exit(); // Close silently
}

// In main ()...
std::set_new_handler(newerrhandler);
Владислав Тончаров
источник