C++memory_resource自定义内存池

2026-04-11 09:50:31 746阅读 0评论

C++ memory_resource:别再手写内存池了,标准库早给你留好了接口

刚接手一个图像处理模块时,我盯着那段反复 new uint8_t[4096]delete[] 的代码看了三分钟——不是它错,是它太“老实”了。每次分配都走系统堆,缓存不友好,还容易碎片化。后来改用 std::pmr::memory_resource,把内存申请逻辑从“每次都去超市买一袋盐”,变成了“自己腌一缸咸菜,随取随用”。这感觉,就像给裸奔的内存管理穿上了合身的工装裤。

memory_resource 不是某种具体内存池,而是一套可插拔的内存分配契约。它定义了两个核心动作:do_allocatedo_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 里释放底层内存:这块内存由 blocks vector 管理,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> 头文件。标准库没给你造好轮子,但它把轮毂、辐条、轴承的标准都定好了——你只需拧上自己的轮圈。

文章版权声明:除非注明,否则均为Dark零点博客原创文章,转载或复制请以超链接形式并注明出处。

发表评论

快捷回复: 表情:
验证码
评论列表 (暂无评论,746人围观)

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

目录[+]