本文最后更新于2 天前,其中的信息可能已经过时,如有错误请自行更正!

C++内存池(MemPool)
1. 什么是内存池
定义:预先分配大块内存并自行管理,提高小对象分配性能。1、减少系统调用;2、提高内存利用率;3、提升缓存命中率。
2. C++内存池框架
#include <iostream>
#include <vector>
#include <cassert>
#include <cstddef>
#include <chrono>
// 对齐到指定的对齐边界,确保返回的地址是对齐的
static inline std::size_t alignUp(std::size_t n, std::size_t align) {
return (n + (align - 1)) & ~(align - 1);
}
class FixedSizePool {
public:
explicit FixedSizePool(std::size_t block_size, std::size_t blocks_per_page = 1024) :
block_size_(adjustBlockSize(block_size)),
blocks_per_page_(blocks_per_page),
free_list_(nullptr) {} // explicit用于修饰类的构造函数或转换函数,禁止隐式类型转换,只能进行显式转换,从而避免意外的类型转换导致的逻辑错误。
~FixedSizePool() {
for (void* page : pages_) {
::operator delete[](page); // 全局删除操作符释放内存
}
}
// 分配一个块
void* allocate() {
if (free_list_ == nullptr) {
expand(); // 如果空闲链表为空,扩展内存
}
Node* head = free_list_;
free_list_ = head->next;
return head;
}
// 释放一个块
void deallocate(void* ptr) {
if (ptr == nullptr) return;
Node* node = static_cast<Node*>(ptr);
node->next = free_list_;
free_list_ = node;
}
std::size_t getBlockSize() const {
return block_size_;
}
std::size_t getBlocksPerPage() const {
return blocks_per_page_;
}
private:
// 调整块大小,确保有至少能存放一个指针的大小,否则无法维护空闲链表
std::size_t adjustBlockSize(std::size_t size) {
std::size_t min = sizeof(void*);
std::size_t a = alignUp(size < min ? min : size, alignof(void*)); // alignof返回输入类型的对齐规则大小
return a;
}
//每次向系统申请一页的内存并把这一页的内存切为很多小块,并把这些小块挂到空闲链表中
void expand() {
// 一整页内存的字节数
std::size_t page_bytes = block_size_ * blocks_per_page_;
char* page = static_cast<char*>(::operator new[](page_bytes)); // 全局new操作符分配内存
pages_.push_back(page); // 记录已分配的页
// 将新页切割成多个块并加入空闲链表
char* block = static_cast<char*>(page);
for (std::size_t i = 0; i < blocks_per_page_; ++i) {
deallocate(block + i * block_size_); // 复用
// char* current_block = page + i * block_size_;
// Node* node = reinterpret_cast<Node*>(current_block);
// node->next = free_list_;
// free_list_ = node;
}
}
struct Node {Node* next;}; // 维护一个单链表,每个节点指向一个空闲块
Node* free_list_; // 空闲块链表头指针,每次分配和释放都从这里操作
std::size_t block_size_; // 每个块的大小
std::size_t blocks_per_page_; // 每页可以分配的块数
std::vector<void*> pages_; // 所有已经分配的“页” - 析构统一释放
};
3. 内存对齐
// 对齐到指定的对齐边界,确保返回的地址是对齐的
static inline std::size_t alignUp(std::size_t n, std::size_t align) { //inline提高性能
return (n + (align - 1)) & ~(align - 1);
}
4. 对象池应用
struct Particle
{
float x, y, z;
int life;
static void* operator new(std::size_t n);
static void operator delete(void* p) noexcept; //noexcept用于指定函数不会抛出异常(或明确声明可能抛出异常),帮助编译器进行优化并增强代码的异常安全性
void update() { ++life; }
};
static FixedSizePool g_particle_pool(sizeof(Particle), 4096);
void* Particle::operator new(std::size_t n) {
return g_particle_pool.allocate();
}
void Particle::operator delete(void* p) noexcept {
g_particle_pool.deallocate(p);
}
5. 测试
int main() {
std::vector<Particle*> vec;
vec.reserve(10000);
auto start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < 10000; i++) {
Particle* p = new Particle{ 0,0,0,0 };
vec.push_back(p);
}
for (auto* p : vec) delete p;
auto end = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
std::cout << "FixedSizePool-生成 10000 Particle 耗时: " << duration.count() << " 微秒" << std::endl;
return 0;
}

