【问题标题】:Preventing memory freeing in STL Container防止 STL 容器中的内存释放
【发布时间】:2013-06-18 10:17:33
【问题描述】:

我有一个不断重复使用的 STL 容器 (std::list)。我的意思是我

  1. 将一些元素推入容器中
  2. 在处理过程中移除元素
  3. 清空容器
  4. 冲洗并重复很多次

在使用 callgrind 进行分析时,我看到大量对 new (malloc) 和 delete (free) 的调用,这可能非常昂贵。因此,我正在寻找一些方法来最好地预先分配相当多的元素。我还希望我的分配池继续增加,直到达到高水位线,并且分配池继续挂在内存上,直到容器本身被删除。

不幸的是,标准分配器会不断调整内存池的大小,因此我正在寻找一些无需我自己编写即可完成上述任务的分配器。

这样的分配器是否存在,我在哪里可以找到这样的分配器?

我在使用 GCC 的 Linux 和使用 STLPort 的 Android 上工作。

编辑:放置new 没问题,我想最小化的是堆行走,这很昂贵。我还希望我的所有对象都尽可能靠近,以尽量减少缓存未命中。

【问题讨论】:

  • new 和 delete 可能是容器中包含的对象析构函数和构造函数,这使得它在某种程度上不可避免
  • @fatih_k 但是,这仅在列表包含指向动态分配对象的指针时才成立。我相信 OP 可能知道要避免这种情况。
  • @Angew,对象的析构函数仅在包含的元素是对象而不是指针时才被调用。 std::container's 不获取指针的所有权
  • placement new 没问题,我只是想尽量减少对 malloc 和 free 的调用

标签: c++ memory-management stl


【解决方案1】:

听起来您可能只是使用了错误类型的容器:对于列表,每个元素占用一个单独的内存块,以允许单独的插入/删除 - 因此列表中的每个添加/删除都需要单独的 @ 987654322@。

如果您可以改用std::vector,那么您可以在添加项目之前reserve 所需的大小。

同样对于删除,通常最好不要单独删除项目。只需在容器上调用clear() 即可清空。它。


编辑:您现在已经在 cmets 中明确表示,您的“在处理期间删除元素”步骤是从列表中间删除元素,并且必须不会使迭代器无效,因此不适合切换到向量。我暂时留下这个答案(为了评论线程!)

【讨论】:

  • 除了我想删除列表中间的元素而不需要 std::vector 需要的内存洗牌
  • @doron contains 里面的顺序有关系吗?如果没有,您可以简单地将要删除的元素与最后一个元素和pop_back() 交换。没有随机播放。
  • @doron 尽管如此,内存洗牌可能比你想象的要少得多。看 Bjarne Stroustrup 解释向量 vs 列表的好处channel9.msdn.com/Events/GoingNative/GoingNative-2012/…(从 44:40 开始)简而言之,即使你删除很多并插入很多,列表中线性搜索的时间完全支配了处理时间,因为对象分散在内存中,这会损害 CPU 缓存。
  • @doron “除了我想删除列表中间的元素而不需要 std::vector 需要的内存洗牌” - 你已经在使用探查器,所以只需冒险测试并将向量的随机播放疯狂的速度与列表的分配疯狂的速度进行比较。结果可能令人惊讶(并且可能会打破您对大 O 表示法的信念;))。
  • @doron 那么对于std::list 来说,这确实是一个完美的(如果不是唯一合理的)用例,并且是您在对该答案的第一条评论中应该提到的,而不是性能参数,因为前者是你不能使用 std::vector 而不是要讨论的伪原因的一个硬性原因。
【解决方案2】:

分配器boost::fast_pool_allocator 设计用于std::list

文档声称“如果您非常关心性能,请在处理std::list等容器时使用boost::fast_pool_allocator,在处理std::vector等容器时使用boost::pool_allocator。”

注意boost::fast_pool_allocator 是一个单例,默认情况下它从不释放分配的内存。但是,它是使用boost::singleton_pool 实现的,您可以通过调用静态函数boost::singleton_pool::release_memory()boost::singleton_pool::purge_memory() 来释放内存。

【讨论】:

  • 这是我感兴趣的方法。我会更详细地看看这个
  • 我喜欢这个主意。我唯一的问题是池是一个单例并且内存永远不会被释放,这在使用我的列表很少使用的长时间运行的程序时是一个问题。
  • 我现在比我更喜欢这个答案!
  • 我最终通过将“next”指针添加到我已经存在的数据结构并自己管理链表来解决这个问题。但是对于一般情况,我最喜欢这个答案。
【解决方案3】:

您可以尝试使用 http://goog-perftools.sourceforge.net/doc/tcmalloc.html 对您的应用进行基准测试,我在一些项目中看到了一些很好的改进(虽然手头没有数字,抱歉)

编辑:似乎代码/下载已移到那里:http://code.google.com/p/gperftools/?redir=1

【讨论】:

  • 我试图完全避免 malloc,因为 malloc 允许所有大小的内存分配。在我的容器中,我进行内存抓取,然后使用所有元素大小相同的事实,这样就不需要堆遍历。
  • 通读他们的测试和基准,他们非常擅长处理小对象,tcmalloc 永远不会减少堆(malloc 总是在需要时增长,free 永远不会放弃给操作系统)
【解决方案4】:

评论太短,所以我将发表我的想法作为答案。

IMO,new/delete 在这种情况下只能来自两个地方。

我相信std::list<T> 是用某种节点实现的,就像通常的列表一样,出于各种原因。因此,每次插入和删除元素都必须导致新/删除节点。此外,如果 T 类型的对象在 c'tor/d'tor 中有任何分配和释放,它们也会被调用。

您可以通过重复现有节点而不是删除它们来避免重新创建标准内容。如果你想把它挤到c级,你可以使用std::vectorstd::vector::reservestd::array

尽管如此,对于在那里创建的每个对象都必须调用一个析构函数。我认为避免创建和破坏的唯一方法是在重复容器时使用T::operator=,或者如果适合您的情况,也可以使用一些 c++13 move 的东西。

【讨论】:

  • 好吧,构造函数和析构函数不一定会导致new/delete,我想重组要包含的元素根本不是OP的选项。
  • @ChristianRau,这就是为什么我说“如果 T 类型的对象有任何分配和释放”。好吧,实际上我打错了。我改写了。当然他们没有new/delete,但如果有,肯定会显化的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-08-04
  • 2011-12-02
  • 1970-01-01
  • 2019-05-12
  • 2015-02-05
  • 1970-01-01
  • 2019-10-25
相关资源
最近更新 更多