1. memory_resource 接口
C++17 引入的 std::pmr::memory_resource 是内存管理领域的一次范式转变——它将"内存分配"抽象为一个可插拔的资源对象,而不是全局的 ::operator new。
1.1 接口定义
namespace std::pmr {
class memory_resource {
public:
// 分配内存,align 是对齐要求(必须是 2 的幂)
void* allocate(size_t bytes, size_t align = alignof(std::max_align_t));
// 释放内存
void deallocate(void* ptr, size_t bytes, size_t align = alignof(std::max_align_t));
// 等价性比较
bool is_equal(const memory_resource& other) const noexcept;
// 虚析构函数
virtual ~memory_resource();
};
}
1.2 标准提供的 memory_resource 实例
| 资源 | 说明 | 适用场景 |
|---|---|---|
new_delete_resource() |
全局堆分配器 | 默认fallback |
null_memory_resource() |
总是返回 nullptr 或抛出 | 测试、禁用分配 |
monotonic_buffer_resource() |
单调递增缓冲区,永不释放 | 短生命周期批处理 |
unsynchronized_pool_resource() |
单线程对象池 | 单线程高性能 |
synchronized_pool_resource() |
多线程安全对象池 | 多线程服务器 |
1.3 自定义 memory_resource 示例
// 一个简单的固定块池分配器
class fixed_block_pool : public std::pmr::memory_resource {
std::vector<std::byte> pool_;
size_t block_size_;
std::vector<void*> free_list_;
size_t alignment_;
public:
fixed_block_pool(size_t block_size, size_t pool_size, size_t alignment = 8)
: block_size_(block_size), alignment_(alignment) {
pool_.resize(pool_size * block_size);
// 初始化自由链表
for (size_t i = 0; i < pool_size; ++i) {
free_list_.push_back(pool_.data() + i * block_size);
}
}
void* allocate(size_t bytes, size_t align) override {
// 只接受固定大小的分配请求
if (bytes > block_size_ || free_list_.empty()) {
throw std::bad_alloc();
}
void* ptr = free_list_.back();
free_list_.pop_back();
return ptr;
}
void deallocate(void* ptr, size_t bytes, size_t align) override {
// 简单放回自由链表
free_list_.push_back(ptr);
}
};
2. 资源池设计
2.1 synchronized_pool_resource
标准库提供的 synchronized_pool_resource 是通用场景的首选,它管理多个不同大小的池:
Small Pool (8-64B)
Medium Pool (64-512B)
Large Pool (512B+)
synchronized_pool_resource 内存布局
// 创建一个池分配器,初始内存来自堆
std::pmr::synchronized_pool_resource pool;
// 使用该池分配内存
std::pmr::vector<int> vec(&pool);
vec.push_back(1);
vec.push_back(2);
// 所有内存通过 pool 分配,不调用全局 new
// pool 析构时一次性释放所有内存
2.2 内存池的内部机制
synchronized_pool_resource 的核心思想是批量获取大块内存,然后切成小块分配:
// 池分配器的工作原理
// 1. 当请求分配小块内存时,先检查是否有空闲块
// 2. 如果没有,从上游资源(通常是 new_delete_resource)批量分配大块
// 3. 将大块切分成多个等大小的块,加入空闲链表
// 4. 释放时只归还到本地链表,不归还给上游
// 5. 当 pool 整体销毁时,才将内存归还给上游
// 优势:
// - 减少 malloc 调用次数(批量分配)
// - 减少内存碎片(同类块相邻)
// - 释放极快(只需放回链表)
2.3 池大小的调优
| 参数 | 说明 | 推荐值 |
|---|---|---|
max_blocks_per_chunk |
每个池每次分配的块数 | 64-256 |
largest_required_pool_block_size |
最大池化块大小 | 根据实际分配模式调整 |
memory_resource |
上游资源 | 通常为 new_delete_resource |
💡 调优建议
使用 pool_options 结构体创建池,可以获得更好的性能:
std::pmr::pool_options opts;
opts.max_blocks_per_chunk = 128;
opts.largest_required_pool_block_size = 4096;
std::pmr::synchronized_pool_resource pool(opts);
3. monotonic_buffer
3.1 单调递增缓冲区的设计
monotonic_buffer_resource 是专为"只分配、不释放"场景设计的:
Allocation Request
→
monotonic_buffer_resource
→
Growing Buffer
monotonic_buffer 只增长,不释放
// monotonic_buffer 的核心特性
std::pmr::monotonic_buffer_resource buffer;
// 或指定初始缓冲区
std::byte initial[4096];
std::pmr::monotonic_buffer_resource buffer(initial, sizeof(initial));
3.2 使用场景
monotonic_buffer 适用于以下场景:
- ✓ 短生命周期批处理:处理完一批数据后整体释放
- ✓ 解析器/编译器:构建 AST 后整体释放
- ✓ 游戏帧数据:每帧创建,下帧覆盖
- ✓ 日志缓冲:批量写入后刷新
3.3 实战例子:JSON 解析
// 使用 monotonic_buffer 进行 JSON 解析
class JsonParser {
std::pmr::monotonic_buffer_resource buffer_;
std::pmr::string parse_buffer_; // 在 buffer_ 上分配
public:
JsonParser() : buffer_(), parse_buffer_(&buffer_) {}
JsonValue parse(const std::string_view& json) {
// 所有解析过程中分配的字符串、数组都来自 buffer_
parse_buffer_.clear();
// ... 解析逻辑 ...
return build_ast(json);
}
// 解析完成后,一次性释放所有内存
void reset() {
buffer_.release(); // 释放整个 buffer
}
};
⚠️ 内存泄漏风险
如果持有 monotonic_buffer_resource 的指针或引用直到程序结束,会导致内存一直增长。使用 RAII 或 reset() 确保及时释放。
4. 碎片优化
4.1 内存碎片化的原因
标准堆分配器在长期运行后会产生碎片,主要原因:
- ✗ 不同大小的分配交错
- ✗ 短生命周期对象在长生命周期对象之间释放
- ✗ 对齐要求导致的内部碎片
4.2 pmr 如何解决碎片
统一内存接口
- ✓ 所有容器使用相同接口
- ✓ 可切换底层分配策略
- ✓ 便于分析和调优
对象池隔离
- ✓ 同大小对象池化
- ✓ 减少外部碎片
- ✓ 批量释放给上游
永不释放模式
- ✓ 完全避免释放
- ✓ 消除碎片
- ✓ 适合批处理
4.3 实战:减少高负载服务器的碎片
// 高性能服务器内存管理架构
class ServerMemory {
// 全局池,用于长期存在的对象
std::pmr::synchronized_pool_resource long_lived_pool_;
// 每个请求的局部池,处理完即释放
std::pmr::monotonic_buffer_resource request_buffer_;
public:
// 处理请求时,使用 request_buffer_
Response handle_request(const Request& req) {
// 在 request_buffer_ 上分配所有临时对象
std::pmr::vector<Item> items(&request_buffer_);
// ... 处理逻辑 ...
// 请求处理完毕,buffer 自动释放
return build_response(items);
}
};
4.4 碎片监控
// 简单的碎片监控实现
class fragmenting_monitor : public std::pmr::memory_resource {
std::pmr::memory_resource* upstream_;
std::atomic<size_t> allocated_{0};
std::atomic<size_t> peak_allocated_{0};
public:
fragmenting_monitor(std::pmr::memory_resource* upstream)
: upstream_(upstream) {}
void* allocate(size_t n, size_t align) override {
auto prev = allocated_.load();
auto desired = prev + n;
peak_allocated_.max(desired);
allocated_.store(desired);
return upstream_->allocate(n, align);
}
void deallocate(void* p, size_t n, size_t align) override {
allocated_.store(allocated_.load() - n);
upstream_->deallocate(p, n, align);
}
size_t current_allocated() const { return allocated_; }
size_t peak_allocated() const { return peak_allocated_; }
};
5. 容器适配
5.1 std::pmr 容器
C++17 提供了一系列使用 pmr 的容器:
| 容器 | pmr 版本 |
|---|---|
std::vector |
std::pmr::vector |
std::string |
std::pmr::string |
std::map |
std::pmr::map |
std::unordered_map |
std::pmr::unordered_map |
std::list |
std::pmr::list |
std::set |
std::pmr::set |
5.2 使用示例
// 使用 pmr::string 的示例
std::pmr::synchronized_pool_resource pool;
std::pmr::string str(&pool);
str = "Hello, ";
str += "World!";
// vector 的示例
std::pmr::vector<std::pmr::string> vec(&pool);
vec.push_back("one");
vec.push_back("two");
vec.push_back("three");
// 所有字符串和 vector 内部都使用 pool 分配
// pool 析构时统一释放
5.3 容器与内存资源的绑定
通过传递不同的 memory_resource,可以实现不同的内存管理策略:
// 策略1: 使用全局堆(默认)
std::pmr::vector<int> v1; // 使用 new_delete_resource
// 策略2: 使用对象池
std::pmr::synchronized_pool_resource pool;
std::pmr::vector<int> v2(&pool);
// 策略3: 使用 monotonic buffer(临时对象)
std::pmr::monotonic_buffer_resource mono;
std::pmr::vector<int> v3(&mono);
5.4 迁移指南
✅ 从常规容器迁移到 pmr 容器的步骤
- 将
std::vector<T>改为std::pmr::vector<T> - 在构造函数中传入 memory_resource 指针
- 确保 memory_resource 的生命周期覆盖容器
- 运行测试验证功能正常
- 性能测试验证内存分配减少
# 迁移检查表
□ 统计所有 std::vector, std::string, std::map 等容器
□ 识别生命周期边界(哪些可以共享 pool)
□ 评估是否需要自定义 memory_resource
□ 添加 Valgrind/massif 监控内存使用
□ 压测验证碎片改善
总结
std::pmr 是 C++ 内存管理的一次重大进步,它将内存分配抽象为可组合、可替换的资源对象。通过正确使用 memory_resource,可以显著减少内存碎片、提高分配性能,并简化内存管理代码。
💡 架构建议
- 服务器应用:使用 synchronized_pool_resource 作为全局池
- 请求处理:每个请求使用独立的 monotonic_buffer,处理完即释放
- 批处理任务:使用 monotonic_buffer,整个任务期间复用同一 buffer
- 高性能路径:考虑自定义 fixed_block_pool