2013-06-25 18 views
11

背景:我开发将被用来作为基地都游戏UTIL /工具创造各种各样的多平台框架。基本的想法是拥有一群工人,每个人都在自己的线程中执行。 (此外,工作人员也可以在运行时产生。)每个线程都有它自己的内存管理器。在具有动态大小内存池的多线程C/C++中实现内存管理器?

我很早就想过创建自己的内存管理系统,我觉得这个项目会很完美,最后再试试。由于这种框架的使用类型,我发现这样的系统适配通常需要实时存储分配(游戏和纹理编辑工具)。

问题:

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

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

  • 移动已分配的数据,并保持有效的指针 - 在堆中分配时,通常情况下,你将获得一个简单的指针内存块。在一个自定义内存管理器中,就我所知,类似的方法是将指针返回到预分配块中的空闲位置。但是,如果预先分配的块太小,需要调整大小或进行碎片整理,会发生什么情况?数据将需要在内存中移动,旧指针将无效。有没有办法以某种方式透明地包装这些指针,但仍然像通常的C++指针一样,将它们用作内存管理的“外部”?

  • 第三方库 - 如果没有办法透明地使用自定义的内存管理系统应用程序中所有的内存分配,每一个第三方库,我与链接,仍然会使用“旧”操作系统内存分配。我了解到,图书馆通常会公开函数来设置库将使用的自定义分配函数,但不保证每个我将使用的函数库都具有这种能力。

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

我也很感谢任何相关的阅读材料,论文,文章和whatnot! :-)

+1

我会添加一些资源作为评论我发现我对未来的读者有兴趣(尽量不要混淆实际问题): http://www.swedishcoding.com/2008/ 08/31 /内存不足/和Jason Gregory撰写的“Game Engine Architecture”一书 - 第5.2节“内存管理”。 – andsve

+1

如果你不需要付钱,也不要重新发明轮子。 – AJMansfield

+1

...或者如果它很有趣。 – manasij7479

回答

2
  1. 准备多个解决方案并让框架的用户采用任何特定的解决方案。您开发的通用分配器的策略类将很好地实现这一点。

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

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

+0

谢谢,这是我所有问题的绝佳答案。正如你所建议的那样,我会让整个管理部分成为可选项,并且如果启用,我将尝试探索不同的解决方案以供选择。有关重载*运算符的好建议,将会考虑这一点。 – andsve

3

如果你正在寻找自己的malloc()/ free()等,你可能应该从检查现有系统的源代码开始,如dlmalloc。然而,这是一个难题,因为它值得。编写你自己的malloc库很难。击败现有的通用malloc库将是Even Harder。

+0

交互式3D图形框架几乎不具备通用性。 – manasij7479

+0

当然,但海报问的是如何最好地实现一个分配器,这个分配器既可以用于他的游戏,也可以用于他的工具链以及不同的工作负载和线程环境。据我所知,他根本不是在要求3D图形框架。 – crowder

+1

但这是他提到的项目的核心。当然,在任何地方实施定制内存管理都不是好主意,但是在分析后使用它可以带来巨大的改进。 – manasij7479

8

作为曾经为过去几代游戏机的AAA游戏编写过许多内存管理器和堆实现的人,让我告诉你它不再值得。

您的信息是旧的 - 在gamecube时代[大约在2003年],我们曾经做过你所说的 - 分配一个大块,并使用自定义算法为每个游戏调整手动划出该块。一旦虚拟内存出现(xbox时代),游戏变得更加复杂[并且因此分配更多并且成为多线程]地址碎片使得这一点变得站不住脚。所以我们切换到自定义分配器来处理某些类型的请求 - 例如物理内存,或锁定空闲的小块低碎片堆或线程本地缓存最近使用的块。

随着内存管理器的建立变得越来越好,比起那些越来越难做得更好 - 当然在一般情况下以及对于特定用例而言更是如此。 Doug Lea Allocator [或任何主流的C++ linux编译器现在提供]和最新的Windows低碎片堆非常好,而且你会在其他地方投入更多时间。

我已经准备了一个电子表格来测量整个分配器负载的所有指标 - 所有的大名字和我多年来收集到的很少一部分。基本上,虽然专业分配器可以赢得一些指标(每个分配的最低开销,空间接近度,最低碎片等),但总体指标是主流指标最好的。

作为您图书馆的用户,我个人喜欢的选择是您只需在需要时分配内存。使用operator new/new运算符,我可以使用标准的C++机制来替换那些并使用我的自定义堆(如果我确实有一个),或者我可以使用平台特定的方式替换您的分配(例如Xbox上的XMemAlloc)。我不需要标记[捕获callstack远远优于我可以做的,如果我想]。当你需要分配内存时,给你一个界面,你可以调用这个界面 - 这只是一个让你实现的痛苦,我可能会把它传递给operator new。你可以做的最糟糕的事情是'最好的',并创建自己的习惯堆。如果内存分配性能出现问题,我宁愿分享整个游戏使用的解决方案,而不是自己推出。

+2

感谢您的深入解释! :-) 我主要是通过Jason Gregory在Game Engine Architecture中撰写的内容,在那里他或多或少地解释了如何在PS3的Uncharted中完成内存管理。不过,我确实承诺说,实际上试图超越操作系统的实施可能是一件坏事。但是我的主要目标和问题主要是要学习如何实现自定义内存分配器,即使性能会非常专用。 因此,如果我决定最后尝试一下,它将成为每个应用程序(和线程)的可选设置。 – andsve

+3

为此,您可以在现代编程中做的最有趣的事情之一就是实现一个堆!如果你生活在这个可能无关紧要的世界里真是太可惜了,不要让它阻止你,尽管它真的很有趣。 *可能*有用。 –

2

不要执行其他内存管理器。

实现在不同种类的使用模式和事件下不会失败的内存管理器非常困难。您可能能够建立一个在您的使用模式下运行良好的特定管理器,但要编写一个适用于许多用户的管理器是一项几乎没有人真正做得好的全职工作。更糟糕的是,实现一个内存管理器非常简单,该管理器在99%的时间内都能正常工作,然后在1%的时间内崩溃,或者由于意外的堆碎片而突然消耗系统中大部分或全部可用内存。

我说这是一个写过多个内存管理器的人,看着多个人编写他们自己的内存管理器,并且看到更多的人尝试写内存管理器并失败。这个问题看起来很困难,并不是因为很难用继承等编写模板化的分配器和泛型类型,而是因为此线程中给出的其他解决方案倾向于在角落类型的负载行为下失败。一旦你开始支持字节对齐(就像所有真实世界的分配器必须的那样),那么堆碎片就会带来丑陋的头脑。适用于小型测试程序的可爱启发式技术,在受到大型真实世界程序的影响时会失败。

一旦你得到它的工作,其他人将需要:cookie来验证内存stomps;堆使用情况报告;内存池;泳池池;内存泄漏跟踪和报告;堆审计;块分裂和合并;线程本地存储; lookasides; CPU和进程级页面错误和保护;设置并检查并清除“空闲内存”模式,也就是0xdeadbeef;以及我无法想象的任何其他事情。

写另一个内存管理器正好落在过早优化的标题下。由于有多个免费的,好的内存管理人员在他们身后进行了数千小时的开发和测试,因此您必须证明您花费自己时间的成本以使结果能够提供某种与其他人相比可衡量的改进已经完成了,你可以免费使用。

如果您确定要实现自己的内存管理器(并希望在阅读此消息后不确定),请详细阅读dlmalloc源文件,然后详细阅读tcmalloc源代码,然后再编译您肯定了解实现线程安全与线程不安全的内存管理器的性能权衡,以及为什么天真的实现往往会导致糟糕的性能结果。