C++memory_resource自定义内存池
C++ memory_resource:别再手写内存池了,标准库早给你留好了接口
刚接手一个图像处理模块时,我盯着那段反复 new uint8_t[4096] 又 delete[] 的代码看了三分钟——不是它错,是它太“老实”了。每次分配都走系统堆,缓存不友好,还容易碎片化。后来改用 std::pmr::memory_resource,把内存申请逻辑从“每次都去超市买一袋盐”,变成了“自己腌一缸咸菜,随取随用”。这感觉,就像给裸奔的内存管理穿上了合身的工装裤。
memory_resource 不是某种具体内存池,而是一套可插拔的内存分配契约。它定义了两个核心动作:do_allocate 和 do_deallocate,只要实现这两个函数,你就拥有了一个合法的、能被标准容器识别的内存资源。关键在于:它让自定义分配策略和容器解耦了。你不用改 std::vector 源码,也不用继承重写一堆模板特化——只需在构造时传入一个 std::pmr::polymorphic_allocator,剩下的交给它调度。
举个实在的例子:假设你有一组固定大小的小对象(比如 64 字节的消息头),频繁 new/delete 会导致堆头开销和碎片累积。这时写一个简单的 FixedBlockResource 就够用:
class FixedBlockResource : public std::pmr::memory_resource {
std::vector<std::unique_ptr<std::byte[]>> blocks;
size_t block_size;
std::byte* free_list = nullptr;
public:
explicit FixedBlockResource(size_t block_sz = 64)
: block_size((block_sz + alignof(std::max_align_t) - 1) & ~(alignof(std::max_align_t) - 1)) {}
protected:
void* do_allocate(size_t bytes, size_t align) override {
if (bytes > block_size || !std::is_power_of_2(align) || align > alignof(std::max_align_t))
return std::pmr::new_delete_resource()->allocate(bytes, align);
if (!free_list) {
auto ptr = std::make_unique<std::byte[]>(block_size * 1024);
blocks.push_back(std::move(ptr));
free_list = blocks.back().get();
// 链式组织空闲块(简化版)
for (size_t i = 0; i < 1023; ++i) {
auto next = free_list + (i + 1) * block_size;
*reinterpret_cast<std::byte**>(free_list) = next;
free_list = next;
}
*reinterpret_cast<std::byte**>(free_list) = nullptr;
}
auto result = free_list;
free_list = *reinterpret_cast<std::byte**>(free_list);
return result;
}
void do_deallocate(void* p, size_t, size_t) override {
*reinterpret_cast<std::byte**>(p) = free_list;
free_list = static_cast<std::byte*>(p);
}
bool do_is_equal(const memory_resource& other) const noexcept override {
return this == &other;
}
};
注意几个实操细节:
- 对齐必须严格守约:
do_allocate接收的align参数不能忽略,哪怕你只做 64 字节块,也要检查是否超出能力范围,超了就 fallback 到new_delete_resource(); do_is_equal是灵魂:它决定了两个 resource 是否可互换。多数场景直接比较地址即可,但若你做了带状态的线程局部池,就得按语义判断;- 不要在
do_deallocate里释放底层内存:这块内存由blocksvector 管理,deallocate只负责归还到你的空闲链表。
真正让这套机制落地的是 std::pmr::polymorphic_allocator。它像一个翻译官,把 std::vector<int> 这类泛型容器的 allocate/deallocate 调用,转译成你 resource 的虚函数调用。用法极简:
FixedBlockResource pool{64};
std::pmr::polymorphic_allocator<int> alloc{&pool};
// 所有内存都走 pool
std::pmr::vector<int> vec{alloc};
vec.reserve(1000); // 分配的 buffer 来自 FixedBlockResource
这里没有宏、没有模板偏特化、没有侵入式改造——你只是换了个 allocator,容器行为就变了。更妙的是,pmr 容器支持拷贝时携带 allocator(默认行为),意味着整个子系统可以统一使用同一池,无需全局变量或单例。
有人问:“我已有成熟内存池,能接入吗?”当然可以。只要你的池暴露 allocate(size_t, size_t) 和 deallocate(void*, size_t, size_t) 接口,包一层 memory_resource 子类,5 分钟就能对接成功。我们团队就把旧的 slab 分配器包装后,直接喂给 std::pmr::unordered_map,QPS 提升 12%,GC 压力几乎归零。
也得说句实在话:memory_resource 不是银弹。它无法绕过操作系统页管理,小对象池对大块分配无益;调试时堆栈里会多一层虚函数跳转;MSVC 对 pmr 的优化一度落后于 GCC(现已基本持平)。但它解决了一个本质问题:把“怎么分配”和“分配给谁”彻底分开。以前我们总在业务逻辑里夹杂 malloc 判空、aligned_alloc 对齐计算;现在这些脏活全收进 resource,主流程干净得像白纸。
下次当你又想为性能手写内存池时,先打开 <memory_resource> 头文件。标准库没给你造好轮子,但它把轮毂、辐条、轴承的标准都定好了——你只需拧上自己的轮圈。


还没有评论,来说两句吧...