【发布时间】:2012-05-28 03:27:31
【问题描述】:
我在我的一个项目中使用了自定义堆实现。它由两个主要部分组成:
-
固定大小的块堆。 IE。仅分配特定大小的块的堆。它分配更大的内存块(虚拟内存页或来自另一个堆),然后将它们划分为原子分配单元。
它执行分配/释放速度很快(在 O(1) 中)并且没有内存使用开销,不考虑外部堆施加的东西。
-
全局通用堆。它由上述(固定大小)堆的桶组成。 WRT 请求的分配大小,它选择适当的存储桶,并通过它执行分配。
由于整个应用程序(大量)是多线程的 - 全局堆在其操作期间锁定适当的存储桶。
注意:与传统堆相比,这种堆不仅需要分配大小,还需要释放。这允许在没有搜索或额外内存开销(例如保存分配块之前的块大小)的情况下识别适当的存储桶。虽然不太方便,但在我的情况下这是可以的。此外,由于“桶配置”在编译时是已知的(通过 C++ 模板 voodoo 实现) - 适当的桶是在编译时确定的。
到目前为止,一切看起来(并且工作)都很好。
最近我研究了一种算法,该算法会大量执行堆操作,并且自然会受到堆性能的显着影响。分析显示其性能受到锁定的显着影响。也就是说,堆本身的工作速度非常快(典型的分配只涉及一些内存取消引用指令),但由于整个应用程序是多线程的 - 适当的存储桶受关键部分保护,它依赖于 interlocked 指令,要重得多。
我同时通过为该算法提供自己的专用堆来解决此问题,该堆不受关键部分的保护。但这在代码级别施加了几个问题/限制。例如需要在堆栈深处传递上下文信息的地方可能需要堆。也可以使用 TLS 来避免这种情况,但在我的具体情况下,这可能会导致重新进入时出现一些问题。
这让我想知道:是否有一种已知的技术可以优化堆以用于(但不限于)单线程使用?
编辑:
特别感谢 @Voo 建议检查 google 的 tcmalloc。
它似乎或多或少地与我所做的相似(至少对于小物体)。但此外,他们通过维护每个线程缓存解决了我遇到的确切问题。
我也想过这个方向,但我考虑过维护每个线程的堆。然后释放从属于另一个线程的堆中分配的内存块有点棘手:应该将它插入某种锁定队列中,并且应该通知另一个线程,并异步释放挂起的分配。异步释放可能会导致问题:如果该线程由于某种原因很忙(例如执行激进的计算) - 实际上不会发生内存释放。此外,在多线程场景中,重新分配的成本要高得多。
OTOH 缓存的想法似乎更简单,更有效。我会努力解决的。
非常感谢。
附注:
确实 google 的 tcmalloc 很棒。我相信它的实现与我所做的非常相似(至少是固定大小的部分)。
但是,为了迂腐,有一个问题是我的堆更胜一筹。根据文档,tcmalloc 会产生大约 1% 的开销(渐近),而我的开销是 0.0061%。准确地说是 4/64K。
:)
【问题讨论】:
-
这让我想起了我多年前所做的测试。常用的“差”标准机制占用了好的“自定义”实现的 100 倍以上。
-
如果比标准内存分配器更快,我很想看看你做了什么。由于大多数标准实现已经完成了您声称要做的事情(以及更多)。我发现 O(1) 声明很奇怪,尤其是当您声明没有开销时(我相信当您的专利通过时,您会赚到一大笔钱)。
-
整个存储桶的想法基本上是 google 的 tcmalloc(尽管这是一个通用分配器,它必须动态决定使用哪个存储桶)。 tcmalloc 确实使用线程本地存储来完全避免您的问题,并且很少从一般堆中分配,因此避免了锁。
标签: c++ c heap-memory