在具有动态大小的内存池的多线程 C/C++ 中实现内存管理器?
Posted
技术标签:
【中文标题】在具有动态大小的内存池的多线程 C/C++ 中实现内存管理器?【英文标题】:Implementing a memory manager in multithreaded C/C++ with dynamically sized memory pool? 【发布时间】:2013-06-25 20:00:10 【问题描述】:背景:我正在开发一种多平台框架,将用作 game 和 util/tool 创建的基础。基本思想是有一个工人池,每个工人都在自己的线程中执行。 (此外,worker 也可以在运行时生成。)每个线程都有自己的内存管理器。
我一直想创建自己的内存管理系统,我认为这个项目将是完美的,最终试一试。由于该框架的使用类型,我发现这样的系统很合适,通常需要实时分配内存(游戏和纹理编辑工具)。
问题:
没有普遍适用的解决方案(?) - 该框架将用于游戏/可视化(不是 AAA,而是独立/游戏)和工具/应用程序创建。我的理解是,对于游戏开发,通常(至少对于控制台游戏)在初始化时只分配一次大块内存,然后在内存管理器内部使用该内存。但这种技术是否适用于更一般的应用?
在游戏中,理论上你可以知道你的场景和资源需要多少内存,但例如,照片编辑应用程序会加载各种不同大小的资源……所以在后一种情况下,内存“块大小”更加动态“需要吗?这就引出了下一个问题:
移动已分配的数据并保持有效指针 - 通常在堆上分配时,您将获得一个指向内存块的简单指针。据我了解,在自定义内存管理器中,类似的方法是返回一个指向预分配块中空闲位置的指针。但是如果预分配的块太小并且需要调整大小甚至碎片整理会发生什么?需要在内存中移动数据,并且旧指针将无效。有没有办法以某种方式透明地包装这些指针,但仍然像通常的 C++ 指针一样在“外部”内存管理中正常使用它们?
第三方库 - 如果无法为应用程序中的所有内存分配透明地使用自定义内存管理系统,我链接的每个第三方库仍将使用“旧的”操作系统内存分配内部。我了解到库公开函数来设置库将使用的自定义分配函数是很常见的,但不能保证我将使用的每个库都具有这种能力。
问题:实现一个可以使用动态大小的内存块池的内存管理器是否可行且可行?如果是这样,碎片整理和内存调整大小如何工作,而不会破坏当前正在使用的指针?最后,如何最好地实施这样的系统以与第三方库一起使用?
我也感谢任何相关的阅读材料、论文、文章等等! :-)
【问题讨论】:
我将添加一些资源作为评论,我发现这些资源对未来的读者很有趣(尽量不混淆实际问题):swedishcoding.com/2008/08/31/are-we-out-of-memory 和《游戏》一书Jason Gregory 的引擎架构 - 第 5.2 节内存管理。 如果您不需要并且没有得到报酬,请不要重新发明***。 @AJMansfield 这样做是为了学习经验和好奇心!也找不到与我的具体问题相关的好答案,我想其他人可能对同样的事情感兴趣。 【参考方案1】:作为之前为过去几代游戏机编写过许多 AAA 游戏的内存管理器和堆实现的人,让我告诉你,它不再值得了。
您的信息已经过时了 - 在 gamecube 时代 [大约 2003 年] 我们曾经按照您所说的去做 - 分配一个大块并使用为每个游戏调整的自定义算法手动分割该块。
随着虚拟内存的出现(xbox 时代),游戏变得更加复杂 [因此需要进行更多的分配并成为多线程] 地址碎片使得这种情况变得站不住脚。所以我们改用自定义分配器来仅处理某些类型的请求 - 例如物理内存、无锁小块低碎片堆或最近使用的块的线程本地缓存。
随着内置内存管理器变得更好,它变得比那些更好 - 当然在一般情况下和特定用例的接近的事情。 Doug Lea Allocator [或任何主流的 c++ linux 编译器现在都带有] 和最新的 Windows 低碎片堆真的非常好,你最好把时间花在其他地方。
我正在使用电子表格来衡量所有分配器负载的各种指标——所有的大牌和我多年来收集的一些。基本上,虽然专家分配器可以在几个指标上获胜(每次分配的最低开销、空间接近度、最低碎片等),但主流的分配器是最好的。
作为您图书馆的用户,我个人的首选方案是您只需在需要时分配内存。使用 operator new
/new
运算符,我可以使用标准 C++ 机制来替换它们并使用我的自定义堆(如果我确实有的话),或者我可以使用特定于平台的方式来替换你的分配(例如 XMemAlloc on的Xbox)。我不需要标记[捕获调用堆栈要优越得多,如果我愿意,我可以做到]。在该列表的下方,您为我提供了一个接口,当您需要分配内存时您将调用该接口 - 这对您来说实施起来很痛苦,无论如何我可能只是将它传递给 operator new 。您能做的最糟糕的事情是“最了解”并创建自己的自定义堆。如果内存分配性能有问题,我宁愿你分享整个游戏使用的解决方案,而不是自己动手。
【讨论】:
感谢您的深入解释! :-) 我主要阅读 Jason Gregory 在 Game Engine Architecture 中所写的内容,在那里他或多或少地解释了在 PS3 的 Uncharted 中如何完成内存管理。但是我确实相信你的话,实际上试图超越操作系统实现可能是一件坏事。但我的主要目标和问题本质上是了解如何实现自定义内存分配器,即使性能非常特定于应用程序。所以,如果我最终决定试一试,它将是每个应用程序(和线程)的可选设置。 去做吧,现代编程中最有趣的事情之一就是实现一个堆!可惜你生活在一个这可能无关紧要的世界,尽管这真的很有趣,但不要让它阻止你。并且可能有用。【参考方案2】:如果您想编写自己的 malloc()/free() 等,您可能应该首先查看现有系统的源代码,例如 dlmalloc。然而,这是一个难题,因为它的价值。编写自己的 malloc 库很难。击败现有的通用 malloc 库将更加困难。
【讨论】:
交互式 3D 图形框架几乎不能作为通用框架。 当然,但发帖人询问如何最好地实现一个分配器,该分配器既适用于他的游戏,也适用于他的工具链,并且适用于不同的工作负载和线程环境。据我所知,他根本没有要求 3D 图形框架。 但这是他提到的项目的核心。当然,在任何地方都实现自定义内存管理是个坏主意,但是在分析之后合理地使用它可以带来巨大的改进。 虽然我正在做一些非常相似的 atm (bitbucket.org/manasij7479/gl) 并且还没有遇到需要我自己的分配器的场景。所以,除非他比我领先得多,否则你的建议有一定的价值。 并非如此——特定于应用程序的池技术对于通用的线程分配器库没有意义。它们当然不会产生一般的效率——通过使用具有特定调整/性能偏差的特定分配器库,特定应用程序可能会在内存局部性、碎片等方面受益匪浅,但另一个应用程序可能会因使用而受到严重影响同一个图书馆。不同的应用程序域需要在其分配器中进行不同的权衡。通用分配器库力求产生良好的全面性能。【参考方案3】:现在,正确答案是:不要再实现另一个内存管理器。
要实现一个不会在不同类型的使用模式和事件下失败的内存管理器是非常困难的。您可能能够构建一个在您的使用模式下运行良好的特定管理器,但编写一个对许多用户运行良好的管理器是一项几乎没有人真正做得好的全职工作。更糟糕的是,实现一个内存管理器非常容易,该管理器在 99% 的时间都可以正常工作,然后在 1% 的时间崩溃或由于意外的堆碎片而突然消耗系统上的大部分或全部可用内存。
我这样说是作为一个写过多个内存管理器的人,看到很多人编写自己的内存管理器,并且看到更多的人尝试编写内存管理器并失败了。这个问题看似困难,不是因为很难编写模板分配器和具有继承等的泛型类型,而是因为该线程中给出的其他解决方案往往会在加载行为的角落类型下失败。一旦你开始支持字节对齐(所有现实世界的分配器都必须),那么堆碎片就会出现它丑陋的头。可爱的启发式算法适用于小型测试程序,但在大型实际程序中失败。
一旦你让它工作,其他人将需要: cookie 来验证内存踩踏;堆使用报告;内存池;池池;内存泄漏跟踪和报告;堆审计;块拆分和合并;线程本地存储;旁观者; CPU 和进程级别的页面错误和保护;设置、检查和清除“空闲内存”模式又名 0xdeadbeef;以及我无法想到的其他任何事情。
编写另一个内存管理器完全属于过早优化的标题。由于有多个免费的、良好的内存管理器,它们背后有数千小时的开发和测试,你必须证明花费你自己的时间成本是合理的,这样结果会比其他人提供某种可衡量的改进已完成,您可以免费使用。
如果您确定要实现自己的内存管理器(希望您在阅读此消息后不确定),请详细阅读 dlmalloc 源代码,然后详细阅读 tcmalloc 源代码,然后确保您了解实现线程安全与线程不安全内存管理器的性能权衡,以及为什么幼稚的实现往往会产生较差的性能结果。
【讨论】:
【参考方案4】:准备多个解决方案并让框架的用户采用任何特定的解决方案。您开发的通用分配器的策略类会很好地做到这一点。
解决此问题的一个好方法是使用重载的 * 运算符将指针包装在一个类中。使该类的内部数据仅作为内存池的索引。现在,您可以在后台线程复制数据后快速更改索引。
大多数good
C++ 库都支持分配器,您应该实现一个。您还可以重载全局 new 以便使用您的版本。请记住,您通常不需要考虑库分配或释放大量数据,这通常是客户端代码的责任。
【讨论】:
谢谢,这是对我所有问题的简短回答。正如您所提议的,我将使整个管理部分成为可选的,并且我将尝试探索不同的解决方案以供选择(如果启用)。关于重载 * 运算符的好建议,将对此进行研究。以上是关于在具有动态大小的内存池的多线程 C/C++ 中实现内存管理器?的主要内容,如果未能解决你的问题,请参考以下文章