使用unsynchronized_pool_resource分配pmr::vector的内存复用问题
我之前尝试结合monotonic_buffer_resource和unsynchronized_pool_resource实现pmr::vector的内存高效分配,避免堆内存调用。但实际运行时,内存并未按预期返回池复用,每次迭代都向monotonic buffer申请内存,很快耗尽;而手动调用allocate/deallocate的示例却能正常复用内存,想知道两者差异和我遗漏的点。
代码示例1(pmr::vector场景)
#include <iostream> #include <memory_resource> #include <vector> class A{ char array[64]; }; typename std::aligned_storage<32000>::type storage; thread_local std::pmr::monotonic_buffer_resource bufferPool{&storage,sizeof(storage),std::pmr::null_memory_resource()}; std::pmr::unsynchronized_pool_resource pool{{},&bufferPool}; int main() { for(int i =0; i < 10000;++i){ std::pmr::vector<A> vec (&pool); vec.reserve(1000); for(int j =0; j < 1000;++j){ vec.emplace_back(); } } }
代码示例2(手动allocate/deallocate场景)
#include <iostream> #include <memory_resource> int main() { char buffer[1024]; std::pmr::monotonic_buffer_resource monotonicResource(buffer, sizeof(buffer)); std::pmr::unsynchronized_pool_resource poolResource(&monotonicResource); for (int i = 0; i < 5; ++i) { std::cout << "Iteration " << i << ":\n"; void* ptr = poolResource.allocate(200); std::cout << "Allocated at: " << ptr << "\n"; poolResource.deallocate(ptr, 200); std::cout << "Deallocated.\n\n"; } return 0; }
差异分析与问题根源
1. 内存申请大小触发的pool行为差异
unsynchronized_pool_resource有一个关键特性:当申请的内存大小超过其默认的「最大池化块阈值」(实现定义,通常为256/512字节)时,会直接向上游内存资源(这里是monotonic_buffer_resource)申请内存,而不会将这块内存纳入自身的池化管理。
- 代码示例2中,每次申请200字节,小于默认阈值,因此pool会从monotonic buffer申请一块大内存块,切割为多个200字节的小块管理。释放时,小块会被放回pool的空闲列表,下一次申请直接复用,无需再向monotonic buffer申请。
- 代码示例1中,
vec.reserve(1000)需要申请1000 * sizeof(A) = 64000字节,远大于默认阈值。因此每次迭代时,pool都会直接向monotonic buffer申请64000字节的大块内存,完全不经过自身的池化复用逻辑。
2. monotonic_buffer_resource的不可回收特性
monotonic_buffer_resource的设计是单向分配:它只能向前分配内存,不支持释放单个内存块(deallocate操作是无效的),只有调用reset()才能重置整个缓冲区的分配位置。
在代码示例1中,每次vector销毁时释放的64000字节大块内存,monotonic buffer无法回收复用——因为它无法将已分配的块标记为空闲。因此每次迭代都会在monotonic buffer的当前位置继续分配新的大块内存,很快耗尽32000字节的缓冲区(甚至第一次申请64000字节就会超过缓冲区大小,触发bad_alloc,除非你的实际缓冲区更大)。
3. 你遗漏的关键配置
要让大内存申请也能被pool池化管理,需要在构造unsynchronized_pool_resource时,通过pool_options手动设置largest_required_pool_block阈值,将你的大申请大小纳入池化范围:
// 调整pool配置,让64000字节的申请也能被池化 std::pmr::pool_options opts; opts.largest_required_pool_block = 64000; // 设置为你需要的最大申请大小 std::pmr::unsynchronized_pool_resource pool{opts, &bufferPool};
这样,pool会为64000字节的申请创建对应的池,释放时将内存块放回池,下一次迭代就能直接复用,无需再向monotonic buffer申请。
另外注意:monotonic_buffer_resource作为pool的上游,当pool需要向其申请chunk时,monotonic会分配一块足够大的内存给pool,pool再切割成对应大小的块管理。即使如此,monotonic本身还是无法回收已分配给pool的chunk,除非调用pool.release()或monotonic.reset(),但只要pool内部的空闲块足够,就不会再向monotonic申请新内存。
内容的提问来源于stack exchange,提问作者getsoubl

