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