整理C++堆分配器和STL

9
我希望编写一个自我碎片整理的内存管理器,它将使用简单的递增堆分配器和简单的紧凑式碎片整理器相结合。粗略的方案是从最低的内存地址向上分配块,并保持记录信息从最高的内存地址向下工作。内存管理器将返回智能指针——boost的intrusive_ptr似乎是最明显的——指向记录结构体,这些结构体本身则指向实际的内存块,从而提供了一定程度的间接性,以便可以轻松地移动块。
碎片整理器将从“代”标记开始压缩堆,以加快进程速度,并每次仅对固定数量的内存进行碎片整理。原始指向块本身的指针在下一次碎片整理之前有效,因此可以自由地传递,从而提高性能。
这种内存分配方案的特定应用是控制台游戏编程,因此在每个帧的开始或结束时,可以相对安全地进行碎片整理。
我的问题是,是否有人在STL中使用过这种分配方案,它会像我想的那样完全破坏STL吗?我可以看到std::list 在intrusive_ptr级别上工作,但是关于STL列表节点本身的分配,有没有办法覆盖next/prev指针使它们自己成为intrusive_ptr,或者我必须在这个更动态的方案旁边拥有一个标准堆分配器。

我不明白。你有一个小的smart_ptr句柄,可以让你移动底层块。但是如果底层块有指针,会发生什么?你如何强制那些指针成为smart_ptrs? - jmucchiello
@jmucchiello:分配器仅返回内存块,由用户确保如果您使用该内存块创建一个类,则该类使用smart_ptrs来管理其可能包含的任何指针。在我的情况下,这是完全可以接受的,因为我对类具有控制权,并且无论如何都使用intrusive_ptrs来“拥有”其他对象。 - user176168
5个回答

6
如果你要在内存中移动对象的话,就不能完全通用地实现。你只能在那些知道它们可能被移动的对象上这样做。此外,你需要一个锁定机制。当对一个对象进行函数调用时,它就不能被移动了。
原因在于整个 C++ 模型都依赖于对象固定在内存中的位置,因此如果一个线程正在调用一个对象的方法,而这个线程被暂停并且对象被移动,那么当线程恢复时就会出现灾难。
任何持有指向另一个可能被移动的对象(包括自身子对象)的原始内存指针的对象都无法工作。
这样的内存管理方案可能有效,但是你必须非常小心。你需要严格实现处理句柄和句柄指向指针的语义。
对于 STL 容器,你可以自定义分配器,但它仍然需要返回固定的原始内存指针。你不能返回一个可能发生移动的地址。因此,如果你使用 STL 容器,它们必须是句柄容器,而节点本身将是普通的动态分配内存。你可能会发现,在句柄间接层、句柄集合的碎片化等开销方面,你获得的收益不如使用传统对象固定在内存中的 C++ 应用程序。
直接使用理解你的句柄的容器可能是前进的唯一方式,即使这样仍然可能会有很多开销。

你只能使用那些知道它们可能被移动的对象来完成这个操作。或者,你可以使用包含了知道它们可能被移动的对象的对象:例如,你可以有一个“托管指针”类,并且你可以使用使用/包含“托管指针”类实例的类,而不是使用裸指针的类。 - ChrisW
他不需要锁定,因为他只会在“帧之间”进行碎片整理,也就是说,在包含托管指针的对象没有被运行时。 - ChrisW
是的,幸运的是我们对应用程序的行为以及程序员使用游戏SDK有相当严格的控制,因此我们可以在合理范围内强制执行某些限制。例如,在应用程序的任何一个更新中,您可以传递原始指针,但它们将在下一帧中无效;如果您想要指向任何需要保留多个帧的内容的指针,则必须使用我们的自定义intrusive_ptr类型。我们使用标准的intrusive_ptr来完成这种类型的事情。 - user176168

0

我的看法是,如果你必须担心碎片化,那就意味着你正在处理占用大量内存的数据片段,因此仅凭这一点,你就不能有太多这样的数据。你已经知道这些将是什么吗?也许降低一个级别并做出更具体的决策会更好,从而对其他代码和应用程序的总体性能产生较小的影响。

列表是放入碎片整理内存管理器的极差示例,因为它是一堆微小的片段,大多数其他STL数据结构也是如此。如果你这样做,它将有各种明显的不良影响,包括碎片整理器的性能下降,间接成本等等。在我看来,唯一有意义的结构是连续的结构 - 数组、双端队列、哈希表的主要块等等,只有在超过一定大小之后,并且在不再调整大小之后。这些类型的问题需要特定的解决方案,而不是通用的解决方案。

请回复评论,告诉我一切进展情况。


0

STL容器使用裸指针实现。

在实例化时,您可以指定自定义分配器(以便它们使用您的分配器初始化指针),但是(由于分配的值存储在裸指针中)您不知道这些指针的位置,因此以后无法更改它们。

相反,您可以考虑自己实现STL的子集:您的STL容器版本可以使用托管指针实现。


对于这个问题,我建议你查看Boost.Interprocess库,其中使用了offset_ptr来处理共享内存区域。 - gimpf
我曾怀疑需要一个自定义的STL实现,这真是遗憾。我想,像EASTL这样正确完成的STL实现可能不会太糟糕,但编写和测试需要相当长的时间。你或其他人是否知道有人编写了这样的STL实现?我似乎记得Palm有一个类似于这样的内存分配方案,我想知道他们是否做了相关的事情... - user176168
这听起来很有趣!想法不错,我得研究一下与数据存储/段中断延迟相关的性能影响以及如果出现问题时调试的可行性。看起来目标硬件在异常发生时会挂起两个硬件线程,这可能是一个加锁方面的优点,但从性能角度来看则是一个负面因素。我不确定与虚拟化程序相关的安全问题也会涉及到什么。我相信ID软件的技术5也在做类似的事情。 - user176168
我非常怀疑EASTL想要这样的内存管理。如果你想要修改自己的STL,我建议从uSTL开始,它简单、紧凑且易读。它是否有效?嗯,显然,我听说Crystal Dynamics在PS2上的Project:Snowblind中使用了它。在模板实例化方面,它节省了超过一兆字节。然而,正如我在其他地方提到的那样,我认为这不是正确的方向。 - 3yE
我也对Palm拥有如此高端的C++滥用东西表示严重怀疑。我不了解Palm,我只知道Epoc->Symbian,我知道他们没有这样做,尽管他们擅长在不崩溃的情况下将系统最大化利用。 - 3yE
显示剩余3条评论

0

另一种相当知名的替代技术是伙伴系统。你可以查看它以获取更多灵感。


0
如果这是用于控制台游戏编程,则更容易在运行时禁止未作用域的动态内存分配。但在启动时,这有点难以实现。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接