ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

mold 内嵌 oneTBB 的 fixed_pool 类深度解析:在定长缓冲区上构建可扩展内存池

mold 内嵌 oneTBB 的 fixed_pool 类深度解析:在定长缓冲区上构建可扩展内存池 mold 内嵌 oneTBB 的 fixed_pool 类深度解析在定长缓冲区上构建可扩展内存池【免费下载链接】moldmold: A Modern Linker 项目地址: https://gitcode.com/GitHub_Trending/mo/mold本文以 oneTBBoneAPI Threading Building Blocks官方规范文档中 fixed_pool Class 为核心结合 mold 仓库内嵌的 memory_pool.h 源码与其在 test_scalable_allocator.cpp 中的测试用例完整讲解fixed_pool的接口语义、Memory Pool 概念、底层实现原理及与memory_pool、memory_pool_allocator的协作方式。读完本文你将掌握如何在固定大小的用户缓冲区上创建线程安全、随处理器数量扩展的内存池并理解其与 STL 容器分配器组合的实战模式。一、fixed_pool 是什么面向固定缓冲区的可扩展内存池fixed_pool是一个用于从固定大小缓冲区进行可扩展内存分配的模板类。与每次分配都直接向系统堆或底层分配器索要内存不同它在构造时一次性接管调用者提供的整个缓冲区之后所有malloc/free/realloc操作都在这个缓冲区内部完成且操作开销会随处理器数量的增加而扩展即多线程并发分配时不会退化为单点串行竞争。从规范文档的 Syntax 与 Header 一节可以看到使用它需要两个前提#define TBB_PREVIEW_MEMORY_POOL 1 #include oneapi/tbb/memory_pool.hTBB_PREVIEW_MEMORY_POOL是一个预览特性开关。这一约束在仓库源码中是被强制执行的在 memory_pool.h 的开头就有#if !TBB_PREVIEW_MEMORY_POOL #error Set TBB_PREVIEW_MEMORY_POOL to include memory_pool.h #endif如果忘记定义该宏编译会直接报错。另外注意本仓库内嵌的头文件将fixed_pool声明在tbb命名空间通过inline namespace v1导出detail::d1::fixed_pool见 memory_pool.h而规范文档示例中使用的是oneapi::tbb::fixed_pool两者在各自的头文件布局下等价使用时以实际引入的头文件为准。二、Memory Pool 概念fixed_pool 遵循的接口契约fixed_pool是Memory Pool 概念Memory Pool Concept的一个模型。规范文档 scalable_memory_pools.rst 用一张表定义了所有内存池类必须满足的伪签名与语义这是理解fixed_pool全部公开成员的基础伪签名语义~P() throw();析构函数释放所有已分配对象的内存void P::recycle();释放所有已分配对象的内存不销毁池本身void* P::malloc(size_t n);从内存池中分配n字节并返回指针void P::free(void* ptr);释放由ptr指定的内存对象void* P::realloc(void* ptr, size_t n);将ptr指向的内存对象重新分配为n字节fixed_pool的完整成员声明摘自 fixed_pool_cls.rstnamespace oneapi { namespace tbb { class fixed_pool : no_copy { public: fixed_pool(void *buffer, size_t size) throw(std::bad_alloc); ~fixed_pool(); void recycle(); void *malloc(size_t size); void free(void* ptr); void *realloc(void* ptr, size_t size); }; } // namespace tbb } // namespace oneapi其中唯一需要单独说明的构造函数语义是fixed_pool(void *buffer, size_t size)构造一个管理buffer所指向内存长度为size字节的内存池。如果运行时构造失败抛出std::bad_alloc异常。no_copy基类意味着内存池持有内部状态底层rml::MemoryPool*句柄不允许拷贝或赋值这与 pool_base 注释中Pool interface is separate from standard allocator classes because it has to maintain internal state, no copy or assignment的说明一致。三、快速上手在 1MB 缓冲区上建立内存池规范文档 Example 给出了最小可运行示例#define TBB_PREVIEW_MEMORY_POOL 1 #include oneapi/tbb/memory_pool.h char buf[1024 * 1024]; oneapi::tbb::fixed_pool my_pool(buf, 1024 * 1024); void* my_ptr my_pool.malloc(10); my_pool.free(my_ptr);要点拆解缓冲区生命周期buf必须在使用期间保持有效池本身不拥有、不释放该缓冲区只负责管理其中的内存一次性接管从源码实现看详见下一节第一次分配请求发生时整个缓冲区会被整体移交给底层池管理器此后所有分配都在该区域内部完成调用方式malloc/free/realloc分别对应 C 运行时同名函数的语义返回的指针可直接用于读写。四、源码级原理fixed_pool 在 tbbmalloc 中的落地fixed_pool的实现位于 memory_pool.h它直接继承自内部基类pool_baseclass fixed_pool : public pool_base { void *my_buffer; size_t my_size; inline static void *allocate_request(intptr_t pool_id, size_t bytes); public: inline fixed_pool(void *buf, size_t size); ~fixed_pool() { destroy(); } };pool_basememory_pool.h把所有公开操作都转发给底层 C 风格rml接口void recycle() { rml::pool_reset(my_pool); } void *malloc(size_t size) { return rml::pool_malloc(my_pool, size); } void free(void* ptr) { rml::pool_free(my_pool, ptr); } void *realloc(void* ptr, size_t size) { return rml::pool_realloc(my_pool, ptr, size); }而rml层Memory Pool 的底层运行时接口定义在 scalable_allocator.h包括pool_create_v1、pool_destroy、pool_reset、pool_malloc、pool_realloc、pool_free等。真正的线程安全与可扩展分配逻辑全部封装在这一层。4.1 构造策略参数与校验构造函数memory_pool.h是理解fixed_pool行为的关键inline fixed_pool::fixed_pool(void *buf, size_t size) : my_buffer(buf), my_size(size) { if (!buf || !size) throw_exception(std::invalid_argument(Zero in parameter is invalid)); rml::MemPoolPolicy args(allocate_request, nullptr, size, /*fixedPool*/true); rml::MemPoolError res rml::pool_create_v1(intptr_t(this), args, my_pool); if (res ! rml::POOL_OK) throw_exception(std::runtime_error(Cant create pool)); }从源码可以推断出三点参数校验缓冲区为空指针或大小为 0 时抛出std::invalid_argument注意规范文档只写了bad_alloc实际实现额外增加了这一校验固定池策略MemPoolPolicy的第 4 个参数fixedPooltrue且pFreenullptr——因为整个缓冲区一次性给出、无需逐个归还大块内存这与 scalable_allocator.h 中对fixedPool位字段all memory consumed at 1st pAlloc call and never returned, no more pAlloc calls after 1st的注释完全对应池创建失败返回码非POOL_OK时抛出std::runtime_error(Cant create pool)典型场景是缓冲区太小不足以容纳池自身的内部元数据。4.2 分配整个缓冲区一次交付fixed_pool的allocate_requestmemory_pool.h展示了定长池与可扩展池的根本差异inline void *fixed_pool::allocate_request(intptr_t pool_id, size_t bytes) { fixed_pool self *reinterpret_castfixed_pool*(pool_id); __TBBMALLOC_ASSERT(0 ! self.my_size, The buffer must not be used twice.); bytes self.my_size; self.my_size 0; // remember that buffer has been used return self.my_buffer; }即底层池管理器第一次需要大块内存时fixed_pool把整个缓冲区作为单个大块交付出去并置my_size 0标记缓冲区已被使用。这解释了为何缓冲区需要足够大——它既要容纳用户分配请求还要容纳池内部的管理结构slab 等。五、与兄弟类配合memory_pool 与 memory_pool_allocatorfixed_pool并非孤立存在同目录的规范文档还定义了另外两个类三者共同构成完整的可扩展内存池体系见 scalable_memory_pools.rst5.1 memory_pool可扩展池与fixed_pool直接接管用户缓冲区不同memory_poolAlloc以模板参数指定的底层分配器为内存来源按需以大块方式向底层索要内存memory_pool_cls.rst。其构造示例#define TBB_PREVIEW_MEMORY_POOL 1 #include oneapi/tbb/memory_pool.h oneapi::tbb::memory_poolstd::allocatorchar my_pool; void* my_ptr my_pool.malloc(10); my_pool.free(my_ptr);从 memory_pool.h 的实现看它把Alloc::allocate/Alloc::deallocate包装成rml::MemPoolPolicy的allocate_request/deallocate_request回调供底层池按需取用。规范文档特别提醒如果底层分配器本身又是另一个可扩展内存池内层池必须在外层池被销毁或 recycle 之前销毁见 memory_pool_cls.rst 的 caution 提示。5.2 memory_pool_allocator把内存池接入 STL 容器memory_pool_allocatorT提供 C 标准分配器接口专门用于让memory_pool/fixed_pool服务于 STL 容器memory_pool_allocator_cls.rst。它与标准分配器的区别是没有默认构造函数必须显式绑定到某个池实例typedef oneapi::tbb::memory_pool_allocatorint pool_allocator_t; std::listint, pool_allocator_t my_list(pool_allocator_t(my_pool));在源码 memory_pool.h 中memory_pool_allocator的allocate直接调用所绑定池的malloc失败时抛出std::bad_allocdeallocate调用池的free。它还实现了rebind、max_size、operator/!比较的是底层池指针是否相同等标准分配器要求。六、测试验证边界行为与嵌套池场景仓库测试 test_scalable_allocator.cpp 给出了大量可验证行为值得逐条对照6.1 极小缓冲区与非法参数TestSmallFixedSizePooltest_scalable_allocator.cpp遍历从 3 字节到 64KB 的各种缓冲区大小池要么可用至少能完成 16 字节或 9KB 的分配要么创建失败抛出异常不会出现创建成功但完全不可用的中间态大小为零的缓冲区会抛出std::invalid_argumentfixed_pool pool(nullptr, 10*1024*1024)也会抛出std::invalid_argument测试注释明确指出无内存的无用分配器不能被创建Useless allocator with no memory must not be created。测试注释还透露了一个容量经验值16 字节的小分配走 16KB 的 slab因此至少要约 16KB 的缓冲区才能支撑小对象分配。6.2 realloc 与回收复用在同一测试文件的主流程test_scalable_allocator.cpp中static char buf[1024*1024*4]; tbb::fixed_pool pool(buf, sizeof(buf)); char *p1 (char*)pool.malloc(16); strcpy(p1, this is a test); char *p2 (char*)pool.realloc(p1, 15); // realloc 保持内容 char *p3 (char*)pool.realloc(p2, sizeof(buf)-128*1024); // 几乎占满整个池 // 循环不断 malloc 翻倍大小并 recycle验证池可反复复用 for (size_t sz 10; sz sizeof(buf); sz * 2) { REQUIRE(pool.malloc(sz)); pool.recycle(); }它验证了realloc的内容保持性、大块再分配内部整理/defragmentation以及recycle()一次回收全部对象后池仍可继续使用的语义。6.3 三层嵌套池测试还构造了一个定长池 → 分配器 → 可扩展池的嵌套用例test_scalable_allocator.cpptypedef tbb::memory_pooltbb::memory_pool_allocatorchar, tbb::fixed_pool NestedPool; static char buffer[8*1024*1024]; tbb::fixed_pool fixedPool(buffer, sizeof(buffer)); tbb::memory_pool_allocatorchar, tbb::fixed_pool fixedPoolAllocator(fixedPool); NestedPool nestedPool(fixedPoolAllocator);即顶层memory_pool的大块内存请求最终由底层fixed_pool从一块 8MB 的静态缓冲区中满足——这正是规范文档 warning 所指的嵌套场景销毁顺序必须内层先于外层。七、使用注意事项总结务必先定义TBB_PREVIEW_MEMORY_POOL否则头文件直接#error拒绝编译缓冲区必须足够大除用户分配需求外池内部管理结构也占用空间极小缓冲区会导致pool_create失败并抛出std::runtime_error缓冲区指针与大小均不能为 0否则构造函数抛std::invalid_argument缓冲区生命周期由调用者负责池在析构时只释放内部管理结构不会free用户缓冲区recycle()一次性释放全部对象比逐对象free更适合阶段式使用模式如每轮迭代结束后整体重置与 STL 容器搭配使用memory_pool_allocator并确保池实例的存活期覆盖容器使用期嵌套池场景遵守先销毁内层池的顺序约束。fixed_pool的价值在于把内存从哪里来一块确定的固定缓冲区与内存怎么高效分配线程安全、可扩展的池化管理彻底解耦非常适合嵌入式缓冲区、共享内存区、以及需要可预测内存足迹的并发场景。若需进一步了解其兄弟类可继续阅读 memory_pool_cls.rst 与 memory_pool_allocator_cls.rst。【免费下载链接】moldmold: A Modern Linker 项目地址: https://gitcode.com/GitHub_Trending/mo/mold创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表