使用动态大小的内存池在多线程C/C++中实现内存管理器?

and*_*sve 11 c c++ memory-management

背景:我正在开发一种多平台框架,它将用作游戏工具/工具创建的基础.基本思想是拥有一个工作池,每个工作线都在自己的线程中执行.(此外,工作人员也可以在运行时生成.)每个线程都有自己的内存管理器.

我一直在考虑创建自己的内存管理系统,我认为这个项目最终会尝试一下.我发现这种系统拟合由于该框架的用法类型通常需要实时内存分配(游戏和纹理编辑工具).

问题:

  • 没有普遍适用的解决方案(?) - 该框架将用于游戏/可视化(不是AAA,而是独立/播放)和工具/应用程序创建.我的理解是,对于游戏开发,通常(至少对于控制台游戏)在初始化时只分配一大块内存,然后在内存管理器内部使用这个内存.但这种技术适用于更一般的应用吗?

    在游戏中你理论上可以知道你的场景和资源需要多少内存,但是例如,照片编辑应用程序将加载所有不同大小的资源......所以在后一种情况下,更动态的内存"块大小"将是需要?这让我想到了下一个问题:

  • 移动已经分配的数据并保留有效指针 - 通常在堆上分配时,您将获得一个指向内存块的简单指针.在自定义内存管理器中,据我所知,类似的方法是返回指向预分配块中空闲区域的指针.但是,如果预先分配的块太小而需要调整大小甚至进行碎片整理,会发生什么?数据需要在内存中移动,旧指针无效.有没有办法以某种方式透明地包装这些指针,但仍然使用它们作为通常"在外部"的内存管理,就好像它们是通常的C++指针一样?

  • 第三方库 - 如果无法透明地将自定义内存管理系统用于应用程序中的所有内存分配,则我链接的每个第三方库仍将在内部使用"旧"OS内存分配.我已经了解到库通常会公开函数来设置库将使用的自定义分配函数,但不保证我将使用的每个库都具有此功能.

问题:实现可以使用动态大小的内存块池的内存管理器是否可行且可行?如果是这样,如何在不破坏当前正在使用的指针的情况下碎片整理和内存调整大小如何工作?最后,如何最好地实现这样一个系统与第三方库一起工作?

我也很感谢任何相关的阅读材料,论文,文章和诸如此类的东西!:-)

Mik*_*ine 9

作为前几代游戏机为AAA游戏编写了许多内存管理器和堆实现的人,让我告诉你它根本不值得.

你的信息是旧的 - 回到游戏立方体时代[大约2003年]我们曾经做过你所说的 - 分配一大块并使用为每个游戏调整的自定义算法手动分割出这个块.

一旦虚拟内存出现(xbox时代),游戏变得更加复杂[并因此进行了更多的分配并变成了多线程]地址碎片使得这种情况无法实现.因此,我们切换到自定义分配器仅处理某些类型的请求 - 例如物理内存,或无锁小块低碎片堆或最近使用的块的线程本地缓存.

随着内置内存管理器变得越来越好,它比那些更难做 - 当然在一般情况下,对于特定用例来说更接近.Doug Lea Allocator [或者现在主流的c ++ linux编译器]以及最新的Windows低碎片堆非常好,你可以更好地将时间投入其他地方.

我已经在工作中使用电子表格来衡量一大堆分配器的各种指标 - 所有大名鼎鼎的,以及我多年来收集的一些.而且基本上虽然专业分配器可以在一些指标上获胜[每个分配的最低开销,空间接近度,最低碎片等],但对于整体指标而言,主流分配器只是最好的.

作为您图书馆的用户,我个人首选的选项是您只需在需要时分配内存.使用operator new/new运算符和我可以使用标准的C++机制来替换它们并使用我的自定义堆(如果我确实有一个),或者我可以使用平台特定的方法来替换你的分配(例如Xbox上的XMemAlloc).我不需要标记[捕获callstacks远远优于我可以做的,如果我想要的话].降低该列表会给你一个接口,当你需要分配内存时,你会给它打电话 - 这只是你实施的一个难题,我可能只是将它传递给operator new.你能做的最糟糕的事情就是"最了解"并创建自己的自定义堆.如果内存分配性能有问题,我宁愿你分享整个游戏使用的解决方案,而不是自己动手.

  • 为此,在现代编程中你可以做的最有趣的事情之一就是实现堆!很遗憾你生活在这个可能不重要的世界里,不要让它阻止你,因为它真的很有趣*.并且*可能*有用. (3认同)
  • 感谢您的深入解释!:-)我主要是通过Jason Gregory在Game Engine Architecture中编写的内容,在那里他或多或少解释了如何在Uncharted for PS3中完成内存管理.但是我确实接受了你的意思,实际上试图超越操作系统实现可能是一件坏事.但我的主要目标和问题主要是学习如何实现自定义内存分配器,即使性能非常特定于应用程序.所以,如果我决定最后尝试一下,它将是每个应用程序(和线程)的可选设置. (2认同)

man*_*479 2

  1. 准备不止一种解决方案,并让框架的用户采用任何特定的一种。您开发的通用分配器的策略类可以很好地做到这一点。

  2. 解决这个问题的一个好方法是用重载的 * 运算符将指针包装在类中。使该类的内部数据仅作为内存池的索引。现在,您可以在后台线程复制数据后快速更改索引。

  3. 大多数goodC++ 库都支持分配器,您应该实现一个。您还可以重载全局 new 以便使用您的版本。请记住,您通常不需要考虑库分配或释放大量数据,这通常是客户端代码的责任。