目录

  1. memory_resource 接口
  2. 资源池设计
  3. monotonic_buffer
  4. 碎片优化
  5. 容器适配

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 适用于以下场景:

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 容器的步骤

  1. std::vector<T> 改为 std::pmr::vector<T>
  2. 在构造函数中传入 memory_resource 指针
  3. 确保 memory_resource 的生命周期覆盖容器
  4. 运行测试验证功能正常
  5. 性能测试验证内存分配减少
# 迁移检查表
□ 统计所有 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