【问题标题】:C++ custom allocator that utilizes a underlying memory pool利用底层内存池的 C++ 自定义分配器
【发布时间】:2011-09-29 14:35:40
【问题描述】:

我正在使用一个内存池类,它重用分配的内存地址和一个自定义分配器,它包装 那堂课。下面的代码 sn -p 让你对接口有一个基本的了解。

template<class alloc>
class memory_pool
    : boost::noncopyable,
      public allocator_traits<void>
{
public:
    memory_pool(typename alloc::size_type alloc_size);
    memory_pool(typename alloc::size_type alloc_size, alloc const&);
    template<typename U> memory_pool(typename alloc::size_type alloc_size,
        typename alloc::rebind<U>::other const&);
    virtual ~memory_pool();

    pointer allocate  (); /*throw(std::bad_alloc)*/
    void    collect   ();
    void    deallocate(pointer) throw(); /*noexcept*/
};

pointer allocate()
{/*
    Checks if a suitable chunk of memory is available in a internal linked list.
    If true, then the chunk is returned and the next chunk moves up.
    Otherwise, new memory is allocated by the underlying allocator.
*/}

void deallocate(pointer)
{/*
    Interprets the passed pointer as a chunk of memory and stores it in a linked list.
    Please note that memory isn't actually deallocated.
*/}

void collect()
{/*
    Effectively deallocates the cunks in the linked list.
    This will be called at least once during destruction.
*/}

当然,这样的需求是有限的。但是,它在您需要的情况下非常有用 到: - 为以非常幼稚的方式使用分配器的类指定分配器类型(例如避免 分配更大的部分,即使它是可取的)。 - 重复分配和释放相同大小的内存。 - 您希望使用分配器的类型非常小(例如 char、short、int 等内置类型)。

理论上,一个实现可以利用一个 memory_pool,它每次需要分配实际分配大小的倍数(来自底层内存管理器)。靠得很近的对象更适合任何缓存和/或预取算法。 我已经实现了这样一个内存池,它有一些开销来处理正确的分配、拆分和释放(我们不能释放用户将传递给释放的每个地址。​​我们只需要释放作为我们拥有的每个内存块开头的地址之前已分配)。

我用以下非常简单的代码测试了这两种情况:

std::list<int, allocator<int>> list;

std::clock_t t = std::clock();
for (int i = 0; i < 1 << 16; ++i)
{
    for (int j = 0; j < 1 << 16; ++j)
        list.push_back(j);
    list.unique();
    for (int j = 0; j < 1 << 16; ++j)
        list.pop_back();
}
std::cout << (std::clock() - t) / CLOCKS_PER_SEC << std::endl;

std::list 每次调用 allocactor::allocate(1, 0) 时都会调用 push_back。 unique() 确保每个元素都会被触摸并与下一个元素进行比较。 然而,结果令人失望。管理按块分配内存池所需的最小开销大于系统获得的任何可能优势。

你能想出一个可以提高性能的场景吗?

编辑: 当然比std::allocator快很多。

【问题讨论】:

  • 请注意包装分配器不能分配数组。

标签: c++ memory-management memory-pool


【解决方案1】:

C++0x 对内存池等scoped allocators 有更好的支持。

分析您的代码。非常很难预测这会带来什么优势,除非您的算法执行非常规则的分配/释放模式,例如 LIFO。

当所有分配的对象大小相同时,编写一个非常快速的分配器非常容易。有一次我写了一些类似

的东西
template <size_t ObjectSize> class allocator {
    // ...
};

template <typename T> class allocator : public allocator <sizeof (T)> {
    // ...
};

在设计分配器之前,您需要确定将分配什么以及如何分配。 operator new 的答案是“anything”和“anyhow”,这就是为什么它有时不是最理想的。如果你不能正确回答这些问题,你的分配器可能不会有很大的改进。

【讨论】:

  • 从您的链接中引用:“可以使用带状态的分配器,比如一个分配器,它包含一个指向要分配的竞技场的指针”。为什么用 C++03 应该是不可能的?
  • 至少对于 STL,分配器被定义为每个类型而不是每个实例。您可以编写自己的有状态分配器,但如果在 std::vector 或其兄弟之一中使用它们,它们将破坏已建立的库实现。
  • 我以前也这样做过(使用ObjectSize 作为编译时表达式)。读起来很好,但限制了使用。您可能已经注意到我在这个阶段只与void* 合作。
【解决方案2】:

你能想出一个可以提高性能的场景吗?

执行大量(每秒 10k+)分配和释放的操作将从中受益 不必每次都遇到复杂的查询来分配/释放,但是,只有当 将分配/释放延迟到组中的组合节省比处理组所需的更多(基本上,您需要通过单位节省来摊销组开销)。

使用连续内存将有助于任何基于节点/指针的结构,如树(但仅限于一点)。然而,现实世界的好处可能截然不同(或根本不存在!) 根据他们的计划,这就是为什么在走上创建这样的自定义系统的道路时,您应该分析您的代码并且已经对如何使用它有一个概念(即:没有意义为分配很少以至于速度增益根本无关紧要的东西制作自定义池分配器。

这样的东西对于调试来说可能很方便,因为你有一个很好的接口来标记内存以监视泄漏和覆盖/无效写入,所以即使它具有与标准系统相同的性能,你也可以通过其他方式获得.

【讨论】:

    猜你喜欢
    • 2012-02-22
    • 1970-01-01
    • 2019-08-11
    • 1970-01-01
    • 2020-03-01
    • 2010-10-24
    • 1970-01-01
    • 2014-01-23
    • 2012-05-29
    相关资源
    最近更新 更多