您如何为内存不足情况做准备?


18

对于范围界定明确的游戏而言,这可能很容易,但是问题是关于沙盒游戏,允许玩家创建和构建任何东西

可能的技术:

  • 使用上限的内存池。
  • 删除不再需要的对象。
  • 在开始时分配额外的内存,以便以后可以将其作为恢复机制释放。我会说大约2-4 MB。

这在移动/控制台平台上更可能发生,在该平台上,内存通常受到限制,与您的16 GB PC不同。我假设您完全控制了内存分配/释放,并且不涉及垃圾回收。这就是为什么要将其标记为C ++。

请注意,我并不是在谈论有效的C ++项目7“为内存不足的情况做准备”,尽管这很相关,但我希望看到一个与游戏开发更相关的答案,您通常会对游戏开发有更多的控制权发生。

总而言之,当您针对内存控制台/移动设备有限的平台时如何为沙盒游戏的内存不足情况做准备?


失败的内存分配在现代PC操作系统上非常罕见,因为当物理RAM用完时,它们会自动交换到硬盘驱动器。不过这应该是可以避免的,因为交换是一种情况比物理内存要慢,并会严重影响性能。
菲利普

@Philipp是的,我知道。但是我的问题更多是针对内存受限的设备,例如控制台和移动设备,我想我提到过。
concept3d 2013年

这是一个相当广泛的问题(也是一种措辞上的民意测验)。您能否将范围缩小一些以更具体地针对单个情况?
MichaelHouse

@ Byte56我编辑了问题。我希望它现在有更明确的范围。
concept3d

Answers:


16

通常,您不会处理内存不足的情况。在像游戏一样庞大和复杂的软件中,唯一明智的选择是尽快崩溃/声明/终止您的内存分配器(尤其是在调试版本中)。在某些情况下,某些核心系统软件或服务器软件会测试并处理内存不足的情况,而在其他情况下通常不会。

如果您有较高的内存上限,则只需确保您所需要的内存永远不会超过该数量。例如,您可以一次保留最大数量的允许NPC,并在达到上限后立即停止生成新的非必需NPC。对于基本的NPC,您可以让它们替换非基本的NPC,或者为设计人员知道要设计的基本NPC提供单独的池/上限(例如,如果您只能拥有3个基本的NPCsa,那么设计人员将不能放置3个以上的NPC)。区域/块-好的工具将帮助设计人员正确地做到这一点,并且测试当然是必不可少的)。

一个真正好的流媒体系统也很重要,尤其是对于沙盒游戏。您无需将所有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)

这是处理发行中意外情况的最后一站措施,但是在调试和测试期间,您可能应该立即崩溃。您不需要依靠这种东西(尤其是因为转储缓存可能会严重影响性能)。

您可能还会考虑转储某些数据的高分辨率副本,例如,如果GPU内存(或共享内存体系结构中的任何内存)运行不足,则可能会转储高分辨率的mipmap级别的纹理。不过,这通常需要大量的架构工作才能实现。

请注意,即使在PC上,某些非常无限的沙盒游戏也很容易崩溃,甚至崩溃(请记住,即使您的PC带有128GB的RAM,常见的32位应用程序的地址空间限制为2-3GB; 64-位操作系统和硬件允许更多的32位应用程序同时运行,但不能做任何事情来使32位二进制文​​件具有更大的地址空间)。最后,您要么拥有一个非常灵活的游戏世界,在任何情况下都需要无限制的内存空间才能运行,要么您拥有一个非常有限且受控制的世界,它始终可以在有限内存(或介于两者之间的某个位置)中完美地运行。


为此答案+1。我写了两个使用Sean风格和离散内存池的系统,它们最终在生产中都运行良好。首先是生成器,该生成器将曲线上的输出回滚到最大限制关闭位置,因此玩家永远不会注意到突然的减少(认为总吞吐量会降低该安全裕度)。其次与块有关,分配失败将强制清除和重新分配。我觉得**一个有限且受控的世界,始终可以在有限的内存中完美运行**对于任何长期运行的客户端都是至关重要的。
Patrick Hughes

+1表示在调试版本中尽可能积极地处理错误。请记住,在调试控制台硬件上,您有时可以访问比零售更多的资源。您可能希望通过在调试设备上专门在零售设备所拥有的地址空间上方分配调试对象来模拟这些条件,并在等效于零售的地址空间用尽时崩溃。
FlintZA

5

通常会在最坏的情况下在目标平台上测试该应用程序,并且您将始终为目标平台做好准备。理想情况下,应用程序应永远不会崩溃,但除了针对特定设备进行优化之外,当您遇到内存不足警告时,别无选择。

最佳做法是预先分配池,游戏从一开始就使用所有需要的内存。如果您的游戏最多可容纳100个单位,而游戏池则可容纳100个单位,仅此而已。如果100台设备超出一台目标设备的内存要求,则可以优化该设备以使用更少的内存或将设计更改为最多90台设备。在任何情况下都不能构建无限的事物,总应该有一个限制。new对于每个实例使用沙盒游戏将非常糟糕,因为您永远无法预测内存使用情况,并且崩溃比限制严重得多。

同样,游戏设计应该始终牢记目标最低的设备,因为如果您在设计中加入“无限”的东西,那么解决内存问题或以后更改设计会变得非常困难。


1

好吧,您可以在启动时甚至在.bss编译时分配大约16个MiB(只能确保100%保证),并使用“安全分配器”,其签名类似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个MiB)失败(再次,将替换为.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),其中myHandlervoid 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);
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.