【问题标题】:Bulk memory free of fragmented stl containers没有碎片化 stl 容器的大容量内存
【发布时间】:2019-02-02 03:52:02
【问题描述】:

目前,当我们想要破坏具有非常碎片化内存分配的复杂对象的非常大的嵌套列表/映射时,我假设 C++ 将调用析构函数并以递归方式逐个释放内存,这需要大量时间且效率低下?

就我而言,我发现有时需要 1 分钟或更长时间才能销毁一个 300GB 的对象。

操作系统可以有效地杀死占用大量内存的进程,因为它只是释放所有内存而不考虑进程内部的逻辑。

我想知道是否有任何现有的 C/C++ 库可以做到这一点?提供一个定制的内存分配器来维护一个 id 系统? 这样,如果我指定一个 id 来为给定的大型 STL 容器(及其元素)创建分配器。当我想破坏它时,我可以释放所有分配给指定 id 的内存,并丢弃指向外部容器的指针(它会跳过所有的析构函数)?就像我们可以“杀死”一个 pid...

谢谢!

【问题讨论】:

  • 操作系统只是取消映射虚拟内存页面,比较快。操作系统不做的是调用任何析构函数,甚至运行您的任何代码。如果您正在处理如此大的数据,那么您可能应该使用自己的自定义分配器从操作系统分配大量内存并在内部处理本地分配而不打扰操作系统。你甚至可以做到这一点,当你完成后,构造函数不必运行,你跳过对象的破坏,只是释放/取消映射大块内存。
  • @Someprogrammerdude 是的,这就是我的意思。只是想知道是否有任何现有的图书馆?我们只是不想重新发明/改造轮子
  • 我们使用池和自定义分配器然后只释放整个池而不是单个对象。你的问题应该被标记为 C 吗?好像不应该。
  • 您可以发布或以其他方式分享此代码吗?我想看看它的实际效果,看看问题是什么。 (如果这真的是一个与分配器相关的问题,而不仅仅是因为析构函数接触了随机内存,我有兴趣尝试一下。)
  • 可能的解决方案:jemalloc 带有 arena 支持(内存池)。通过覆盖 C++ new 运算符以使用带有标志的 jemalloc 的 mallocx 函数来使用特定的竞技场。创建一个 arena 并在将元素插入容器之前切换到 arena 并在插入后切换回默认值。在不再需要时摧毁竞技场

标签: c++


【解决方案1】:

这可以通过池分配器和新放置来完成,当然您会有一些限制,例如为池中的插槽找到一个通用大小(如果您不想要细粒度),但通常是一个简单的情况场景如下:

struct Foo {
  double x, y;
  Foo(double x, double y) { this->x = x; this->y = y; };
};

std::byte* buffer = new std::byte[sizeof(Foo) * 10];

Foo* foo1 = new(buffer) Foo(1.0, 2.0);
Foo* foo2 = new(buffer + sizeof(Foo)) Foo(1.0, 2.0);

delete[] buffer;

解释基本原理。不过,这必须采取预防措施,因为没有人调用您的析构函数(您应该通过foo1->~Foo() 手动执行此操作)。但是,如果析构函数没有副作用或者您可以立即处理它们,那么标准允许您不要显式调用它。

现在棘手的部分是,如果您使用的是 STL 集合,那么它们会在内部进行大量分配来存储它们的需求(尤其是像 std::mapstd::list 这样的容器)。所以你需要编写一个自定义的allocator<T> 来包装一个高效的池化方案。

【讨论】:

  • 谢谢!但是一个好的内存分配器并不是那么容易编写和维护。将自定义分配器传递到每个嵌套容器的深处太复杂了……覆盖 new 运算符也可以解决问题,但是需要一些新的全局变量,而且它很hacky。所以我期待是否已经有一个图书馆。
  • 覆盖 new 操作符一点也不 hacky,它被大量使用,尤其是在池分配器相当普遍的游戏开发中。
【解决方案2】:

如果您想要高效的freeing 内存,则执行一次删除是可行的方法。但请记住,释放内存并不是 delete 调用所做的唯一事情。它也调用析构函数。如果不是微不足道或不可见,您的编译器仍然必须通过函数调用来调用它。

也就是说,尽可能使用std::vector。我已经在矢量之上编写了自定义集和地图,功能较少(不删除),以获得内存和性能。

如果您有很多小对象,例如通常需要 1、2、... 16 个元素的向量,则可以通过使用更多内存来提高速度。 boost small vector and other containers 可以帮助你不分配。在算法中使用它,已经为我节省了在现实世界代码中的显着百分比 (>90%)。

最后,你不可能总是赢。如果您可以估计内存使用情况,或者附近已经有一些东西,您可以使用 [https://howardhinnant.github.io/stack_alloc.html](stack Howard Hinnant 的分配器)。不要被名字所迷惑,你也可以在堆上分配内存。通过一些操作,我怀疑您应该能够将其更改为接受运行时大小。不过,它并不完美,您可以涵盖大部分内容。

也就是说,您总是可以故意制造内存泄漏,尽管这可能会消除析构函数的副作用。您可以将 extract the nodes 从您的地图中取出,并将它们存储在分配的向量中。这可能涉及 UB,我不是这方面的专家。

哦,最后,您可以从标准分配器继承,并且只覆盖 deallocate 函数。查看全局以决定是否要调用实际的解除分配。拨动开关,在退出时做内存泄漏。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-08-04
    • 2022-01-14
    • 2014-09-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多