防止多态容器中的内存碎片
Preventing Memory Fragmentation in Polymorphic Container
这个问题需要一些解释,所以如果你不跳过这个例子,请竖起大拇指:)
我最近读了一本书,详细描述了内存碎片(堆上),它让我思考自己的项目。例如,当以以下方式使用ptr_container(来自Boost)时
ptr_array<A> arr; //
arr.push_back(new A()); // a1.
arr.push_back(new A()); // a2.
arr.push_back(new A()); // a3.
....
在替换元素时,它将很快导致一些内存碎片。为了便于论证,假设实际的容器可以容纳我们给它的所有指针
[arr_array][a1][a2][a3]...[aN]
当我们开始用一个子类型(具有更大的大小)替换指针时,这种情况发生了变化,假设我们将奇数指针(a1,a3,…)引用的所有对象替换为一个更大的类型,那么它看起来像:
[arr_array][xx][a2][xx][a4]...[aN][b1][b3]...[bN]
其中[xx]表示未使用的空间,b1…bN是新对象。
所以我想要的是一个存储对象的容器(比如STL容器),但支持多态存储。我知道如何实现这种容器,但我更喜欢使用"专家"库(Boost,STL,…)
是否有一个容器支持(动态分配的)保存在连续内存序列中的多态对象
例如,内存布局可能如下所示:
[arr_array] = [ptr_lookup_table][storage]
= [p1, p2, ..., pn][b1, a2, b3, ..., aN]
感谢您的回答/评论!
内存碎片需要预先了解内存分配,所以我需要先设置一些概念。
内存分配
当您调用运营商new
(默认情况下,它通常会在后台调用malloc
)时,您不会直接向操作系统请求内存,相反(通常)会发生以下情况:
- 您调用
malloc
76个字节,它看起来是否可用:- 如果不是,它会从操作系统请求一个页面(通常为4KB),并准备
- 然后它会为您提供您要求的76个字节
内存碎片可能发生在两个级别:
- 你可能会耗尽你的虚拟地址空间(使用64位平台就不那么容易了)
- 您可能有几乎为空的页面,这些页面无法回收,但无法满足您的请求
一般来说,由于malloc
应该一次调用4KB的页面(除非你要求更大的块,在这种情况下,它会选择更大的4KB倍数),所以你永远不应该耗尽你的地址空间。不过,它发生在32位机器(限制为4GB)上,用于异常大的分配。
另一方面,如果malloc
的实现过于天真,那么最终可能会出现碎片化的内存块,因此内存占用比实际使用的内存占用要高得多。这通常是术语内存碎片现在所指的。
典型策略
当我说天真时,我指的是,例如,你不断分配一切的想法。这是个坏主意。这通常是而不是发生的情况。
相反,现在优秀的malloc
实现使用池。
通常,每个大小都有池:
- 1字节
- 2字节
- 4字节
- 512字节
- 4KB及以上是专门(直接)处理的
当你提出请求时,他们会找到能满足你的最小大小的池,这个池会为你服务。
因为在一个池中,所有请求都以相同的大小提供服务,所以池中没有碎片,因为空闲单元可以用于任何传入请求。
那么,碎片化
如今,你不应该观察碎片本身。
然而,你仍然可以观察到内存漏洞。假设一个池正在处理9到16字节的对象,并且您分配了4000000个对象。这需要至少16000页4KB。现在假设您解除分配除16000个对象之外的所有对象,但不小心使每个页面仍然存在一个对象。操作系统无法回收这些页面,因为您仍在使用它们,但由于4KB中只使用了16个字节,因此(目前)空间相当浪费。
一些带有垃圾回收的语言可以用压缩来处理这些情况,但是在C++中,由于用户可以直接控制对象地址,因此无法在内存中重新定位对象。
魔术容器
我不知道有这样的野兽。我也不明白为什么它会有用。
TL;DR
不要担心碎片化。
注意:"专家"可能想编写自己的池分配机制,我想提醒他们不要忘记对齐
碎片发生而不是,因为使用了boost容器。当您经常使用new
和delete
分配和解除分配不同大小的对象时,就会发生这种情况。ptr_array
只是存储指向这些分配对象的指针,并且可能不会显著地导致碎片化。
如果您想对抗内存碎片,可以重载对象operator new
并实现自己的内存管理系统。我建议你阅读内存池和空闲列表的主题。
(编辑:对不起,误解了你的问题;之前的答案已删除。)
您可以为对象使用任何内存池。通常,您将相同(或相似)大小的对象组合在同一个池中。由于您通常必须在池上调用一个特殊的delete
函数,因此我建议您使用带有自定义deleter的shared_ptr
。然后,您可以将这个shared_ptr
与任何您喜欢的标准容器一起使用。
编辑:似乎需要一个例子。警告:这完全是未经测试的,而且是我的想法。不要指望它会编译。
#include <boost/pool.hpp>
#include <memory>
class A;
class B; // B inherits from A
int main() {
// could be global
boost::object_pool<A> a_pool;
boost::object_pool<B> b_pool;
std::vector<std::shared_ptr<A>> v;
v.push_back(std::shared_ptr<A>(a_pool.construct(), [&](A*p) { a_pool.free(p); }));
v.push_back(std::shared_ptr<A>(a_pool.construct(), [&](A*p) { a_pool.free(p); }));
v.push_back(std::shared_ptr<A>(a_pool.construct(), [&](A*p) { a_pool.free(p); }));
v[2] = std::shared_ptr<B>(b_pool.construct(), [&](B*p) { b_pool.free(p); });
return 0;
}
即使B比A大得多,这也会起作用。它也不依赖于自动释放池,这是IMHO的危险。内存布局并不紧张,因为池总是会过度分配,但它不会有碎片,如果我理解你的问题,这就是你想要的。
- 多态性和功能结合
- 具有默认模板参数的多态类的模板推导失败
- 使用连续内存实现多态性
- 如何为多态性中的指定类型分配内存
- 分配分配器为多态对象分配内存
- 通过指向非多态类型的基类的指针获取已分配内存的地址
- 是否可以将多态性类存储在共享内存中
- 返回不带动态内存分配的多态类型
- C++删除运算符如何查找多态对象的内存位置
- 具有数字集C++的多态性中的内存泄漏
- 内存泄漏和多态性
- C++内存分配器和多态类型
- 防止多态容器中的内存碎片
- 删除多态对象和内存泄漏
- 使多态性在C++映射中工作,而不会发生内存泄漏
- 使用指示器的c++和Java之间SWIG多态性中的内存泄漏
- 我如何使连续内存以一种安全的方式表现多态
- 动态多态内存容器-返回值不正确
- 以多态方式使用派生类的std::vector成员的复制赋值会导致内存泄漏
- 多线程是否强调内存碎片