在 c 中,为了优化性能,减少频繁的内存分配和释放操作,可以自定义一个内存池分配器,并将其与 stl 容器结合使用。以下是实现自定义内存池分配器的完整教程。
1. 为什么需要自定义内存池分配器
- 减少动态分配的开销:
- 使用
new
或malloc
动态分配内存会带来较大的开销,尤其是在频繁分配和释放小块内存时。 - 内存池通过预分配大块内存并按需管理小块,可以显著减少这种开销。
- 使用
- 提升内存访问性能:
- 内存池分配的内存通常是连续的,这有利于缓存友好性(cache locality),从而提高运行速度。
- 自定义分配器与 stl 容器结合:
- stl 容器(如
std::vector
,std::map
等)支持自定义分配器,用于控制内存分配和释放行为。 - 使用内存池分配器可以优化 stl 容器的性能。
- stl 容器(如
2. 内存池分配器的设计思路
一个简单的内存池需要包含以下功能:
1. 预分配内存块:
– 在初始化时,分配一大块连续内存,用于后续的小块分配。
- 小块内存分配:
- 从已分配的内存块中划分小块,按需分配给用户。
- 内存释放和重用:
- 用户释放的小块内存可以回收到内存池中,以便再次分配。
3. 实现自定义内存池分配器
以下是完整实现,包括内存池类和与 stl 容器结合使用的分配器。
3.1 内存池实现
#include
#include
#include
#include
class memorypool {
public:
memorypool(size_t blocksize, size_t blockcount)
: _blocksize(blocksize), _blockcount(blockcount) {
allocateblock();
}
~memorypool() {
for (void* block : _memoryblocks) {
::operator delete(block); // 释放所有内存块
}
}
void* allocate() {
if (_freeblocks.empty()) {
allocateblock(); // 如果没有空闲块,分配新的内存块
}
void* ptr = _freeblocks.back();
_freeblocks.pop_back(); // 从空闲块中取一个
return ptr;
}
void deallocate(void* ptr) {
_freeblocks.push_back(ptr); // 将释放的块放回空闲列表
}
private:
size_t _blocksize; // 每个小块的大小
size_t _blockcount; // 每个内存块包含的小块数
std::vector _freeblocks; // 空闲的小块列表
std::vector _memoryblocks; // 已分配的内存块
void allocateblock() {
// 分配一整块内存
void* block = ::operator new(_blocksize * _blockcount);
_memoryblocks.push_back(block);
// 将分配的内存划分为小块,加入空闲列表
for (size_t i = 0; i < _blockcount; i) {
_freeblocks.push_back(static_cast(block) i * _blocksize);
}
}
};
3.2 自定义分配器
实现符合 stl 的分配器接口(即 std::allocator
的行为)。
template
class poolallocator {
public:
using value_type = t;
poolallocator(size_t blockcount = 1024)
: _pool(sizeof(t), blockcount) {}
template
poolallocator(const poolallocator& other) noexcept
: _pool(other._pool) {}
t* allocate(size_t n) {
if (n != 1) {
throw std::bad_alloc(); // 只支持单个对象的分配
}
return static_cast(_pool.allocate());
}
void deallocate(t* ptr, size_t n) {
if (n != 1) {
return; // 只支持单个对象的释放
}
_pool.deallocate(ptr);
}
template
struct rebind {
using other = poolallocator;
};
private:
memorypool& _pool;
template
friend class poolallocator;
};
3.3 将内存池分配器与 stl 容器结合
可以将自定义分配器与任意支持分配器的 stl 容器结合使用,例如 std::vector
、std::list
、std::map
等。
示例:使用 std::vector
和内存池分配器
#include
#include
int main() {
// 使用自定义内存池分配器
poolallocator allocator;
// 将分配器绑定到 std::vector
std::vector> vec(allocator);
// 插入数据
for (int i = 0; i < 10; i) {
vec.push_back(i);
}
// 输出数据
for (int value : vec) {
std::cout << value << " ";
}
std::cout << std::endl;
return 0;
}
3.4 输出结果
运行代码时,std::vector
将使用自定义的内存池分配器来管理内存,输出如下:
0 1 2 3 4 5 6 7 8 9
4. 优化和注意事项
- 线程安全:
- 如果需要在多线程环境下使用,可以为
memorypool
添加互斥锁(如std::mutex
)来保护_freeblocks
和_memoryblocks
。
- 如果需要在多线程环境下使用,可以为
- 对象构造与析构:
- 支持多类型内存分配:
- 内存池可以设计为支持不同大小的对象分配,通过模板实现多类型支持。
- 内存池清理:
- 在析构时释放所有内存,确保没有内存泄漏。
5. 总结
- 使用自定义内存池分配器可以显著提高小内存分配的性能,特别是在高频率内存分配释放场景中。
- 自定义内存池分配器需要实现分配(
allocate
)和释放(deallocate
)接口,并与 stl 容器结合。 - 在多线程环境中使用时,需要考虑线程安全性。
- 内存池适合性能敏感的场景,但在普通场景下可以优先使用标准分配器(
std::allocator
)。