我正在使用一个内存池类,它重用已分配的内存地址和一个自定义分配器 那个班。以下代码段为您提供了界面的基本概念。
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.
*/}
当然,对此类需求的限制是有限的。但是,它在您需要的情况下非常有用 至: - 为一个以非常天真的方式使用该分配器的类指定一个分配器类型(例如Avoids 即使是可取的,也要分配较大的碎片。 - 重复分配和释放相同大小的内存。 - 您希望使用分配器的类型是非常小的(例如内置类型,如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
快得多。
答案 0 :(得分:1)
C ++ 0x更好地支持scoped allocators,例如内存池。
对您的代码进行概要分析,除非您的算法执行非常规则的分配/解除分配模式(如LIFO),否则非常很难预测这会带来什么好处。
当所有分配的对象大小相同时,编写一个非常快速的分配器是很容易的。一旦我按照
的方式写了一些东西template <size_t ObjectSize> class allocator {
// ...
};
template <typename T> class allocator : public allocator <sizeof (T)> {
// ...
};
在设计分配器之前,您需要确定将分配什么以及如何分配。 operator new
的答案是“任何东西”和“无论如何”,这就是为什么它有时是次优的。如果你不能正确回答这些问题,你的分配器可能不会有很大的改进。
答案 1 :(得分:0)
你能想到一个会提高性能的场景吗?
做出很多(每秒10k +)分配和解除分配的东西都会受益 每次都不必为了分配/释放而遇到复杂的查询 将allocs / frees延迟到组中的组合节省比处理组所需的更多(基本上你需要通过每单位节省来分摊该组)。
连续内存的使用将有助于任何基于节点/指针的结构,如树(但仅限于一个点)。然而,现实世界的好处可能大不相同(或根本不存在!) 从他们计划的地方来看,这就是为什么在创建这样的自定义系统的道路上,你应该对你的代码进行剖析,并且已经考虑了它将如何被使用(即:没有任何意义)为那些进行如此少的分配的东西制作一个自定义池分配器,速度增益根本不重要。)
这样的东西可以很方便调试,因为你有一个很好的界面来标记内存以监控泄漏和覆盖/无效写入,所以即使它具有与标准系统相同的性能,你也可以通过其他方式获得