ARTICLE DETAIL

资讯详情

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

CANN opbase 算子开发之 Object 基类:预留内存管理接口的定位、实现与调用链解析

CANN opbase 算子开发之 Object 基类:预留内存管理接口的定位、实现与调用链解析 CANN opbase 算子开发之 Object 基类预留内存管理接口的定位、实现与调用链解析【免费下载链接】opbase本项目是CANN算子库的基础框架库为算子提供公共依赖文件和基础调度能力。项目地址: https://gitcode.com/cann/opbase本篇技术指南聚焦 CANN opbase 算子库基础框架中的op::Object类接口。Object是算子开发框架中一组预留接口通过重载operator new/operator delete为派生对象提供统一的内存申请与释放入口。读完本文你将掌握Object四个接口的语义与使用边界、其在 object.h 中的声明形式以及背后经由 bridge_pool.cpp 的 BlockCache 与 HugeMemPool 双路径内存分配机制并了解仓库 UT 对分配结果对齐性的验证方式。接口定位一组不建议开发者使用的预留接口Object属于opdev命名空间下的基础能力之一其文档位于 object.md。文档开篇即明确本章接口为预留接口后续有可能变更或废弃不建议开发者使用开发者无需关注。这是理解Object的关键前提它不是面向算子开发者的稳定公开 API而是框架内部为统一对象内存管理预留的基础设施。文档提供的接口列表如下接口定义功能说明Object()Object 的构造函数。new(size_t size)Object 的内存申请函数。new(size_t size, [[maybe_unused]] const std::nothrow_t tag)Object 的内存申请函数。delete(void *addr)Object 的内存释放函数。从使用约束上讲开发者不应在自定义算子代码中直接依赖这些接口未来版本可能对其签名、语义乃至存在性进行调整。头文件中的接口形态四组重载的声明细节Object的完整声明位于 object.h其形态比文档表格展示的更为完整。核心声明如下namespace op { class Object { public: Object() default; virtual ~Object() default; public: void* operator new(size_t size) throw(); void* operator new[](size_t size) throw(); void* operator new(size_t size, [[maybe_unused]] const std::nothrow_t tag) throw(); void* operator new[](size_t size, [[maybe_unused]] const std::nothrow_t tag) throw(); void operator delete(void* addr); void operator delete[](void* addr); }; } // namespace op对照文档表格可以从源码层面补充几点事实构造与析构构造函数与析构函数均以 default形式提供其中析构函数为virtual说明Object的设计意图是作为基类被派生类继承通过虚析构保证派生对象释放时正确调用到完整的析构链。对象级与数组级重载除文档表格列出的单对象形式外头文件同时重载了operator new[]/operator delete[]覆盖new T[n]与delete[]的数组分配场景二者在实现上指向同一套底层逻辑。nothrow形态带const std::nothrow_t参数的版本对应new (std::nothrow) T的调用方式配合throw()动态异常说明语义上保证分配过程不向外抛异常。头文件依赖源码通过#include new引入std::nothrow_t通过#include cstddef引入size_t并以using std::size_t;注入当前作用域。实现要点所有重载汇聚到同一对内部接口Object的成员函数实现集中在 object.cpp代码极其精简全部重载最终汇聚到op::internal命名空间下的Allocate/DeAllocate两个内部函数void* Object::operator new(size_t size) throw() { return op::internal::Allocate(size); } void* Object::operator new[](size_t size) throw() { return op::internal::Allocate(size); } void* Object::operator new(size_t size, [[maybe_unused]] const std::nothrow_t tag) throw() { return op::internal::Allocate(size); } void Object::operator delete(void* addr) { op::internal::DeAllocate(addr); } void Object::operator delete[](void* addr) { op::internal::DeAllocate(addr); }从源码结构可以推断出几点设计意图size参数在 nothrow 版本中被标记为[[maybe_unused]]且tag同样未参与逻辑说明两种形态在分配行为上完全等价nothrow 语义由throw()异常规格与内部实现共同保证而非在重载内部做分支处理。数组与单对象共享同一实现框架不区分对象级与数组级分配的内存布局简化了底层内存池的管理模型。内部接口的声明位于 bridge_pool.h与GetPoolIndex、UpdateHugeMemIndex、FreeHugeMem、CheckDoubleFree等函数同属op::internal内存管理工具集说明Object的内存语义与框架的线程本地内存池体系深度绑定。底层调用链BlockCache 与 HugeMemPool 双路径分配Allocate/DeAllocate的实现位于 bridge_pool.cpp其核心逻辑是依据线程本地上下文中的内存池索引做路径分派void* Allocate(size_t size) { int32_t id op::internal::GetThreadLocalContext().poolIndex_; if (id ! op::kInvalidHugeMemIndexId) { return GetAddr(id, size); } else { return op::internal::BlockCache::CacheAlloc(size); } } void DeAllocate(void* addr) { OP_CHECK(addr ! nullptr, OP_LOGW(The address passed to DeAllocate is nullptr.), return); if (op::internal::BlockPool::InHugeMemRange(addr)) { // since huge mem pool use offset, so free just a dummy operation } else { op::internal::BlockCache::CacheFree(addr); } }由此可以得到一条清晰的调用链事实路径分派依据分配走哪条路径由线程本地上下文poolIndex_决定。索引为op::kInvalidHugeMemIndexId时走 BlockCache块缓存池否则走 HugeMemPool大页内存池的GetAddr偏移寻址。释放的对称设计释放时通过BlockPool::InHugeMemRange判断地址是否落在大页区间——大页池按偏移管理释放是空操作非大页区间则归还给BlockCache。空指针防护DeAllocate入口对空指针做OP_CHECK校验并打印告警日志后直接返回避免释放空指针导致未定义行为。双重释放防护bridge_pool.cpp中还实现了CheckDoubleFree通过BlockStore::BlockHeader的magic_字段与cacheExt_状态位区分活跃 block已归还缓存链表跨线程 free等场景为内存安全提供额外看护。分配结果的对齐契约UT 用例的实证仓库在 test_alignment.cpp 中为Object::operator new的对齐行为编写了专门的单元测试这为对象地址满足STD_MAX_ALIGN对齐这一实现事实提供了直接证据。测试注释明确说明该用例看护 Issue#318 的对齐修复——x86_64 GCC 13 下movaps指令写 8 mod 16 地址触发 SIGSEGV 的崩溃现场。测试覆盖了四条路径test_alignment.cppBlockHeaderSizeIsAligned编译期static_assert与运行期断言双重看护BlockHeader大小为STD_MAX_ALIGN定义于 common_utils.h即alignof(std::max_align_t)的整数倍、对齐度不小于STD_MAX_ALIGN。MixedSequence_BlockCachePath在aclCreateTensor→new aclOpExecutor→new aclTensor交织循环 20 轮的场景下验证 BlockCache 路径返回地址 16 对齐且释放无异常。MixedSequence_HugeMemPath通过InitHugeMemThreadLocal激活大页路径额外断言InHugeMemRange为真验证 offset 累加后地址仍保持对齐。PathSwitch_BlockCacheToHugeMem模拟线程先从 BlockCache 路径运行、后切换到大页路径的真实 executor 场景。该测试同时印证了Object的实际应用位置aclTensor、aclOpExecutor等框架对象经由Object::operator new获得内存例如 kernel_tensor.h 中struct aclTensorExtend : public Object的继承关系。使用建议与注意事项综合以上文档与源码事实在使用层面给出如下建议遵循预留接口约束Object接口可能随版本变更或废弃算子实现应避免直接依赖其签名优先使用框架提供的高层 API如aclCreateTensor完成对象创建。理解而非绕过尽管不建议直接调用理解Object的分配语义有助于排查算子对象创建、释放相关的内存问题——例如地址对齐异常、跨线程释放、双重释放等均可从 bridge_pool.cpp 与 block_pool.h 的实现中找到线索。继承场景注意虚析构自定义类若继承Object其虚析构会被自动纳入delete释放链路释放顺序应遵循对象创建的反序与 UT 中delete directTensor; delete exec; aclDestroyTensor(apiTensor);的模式一致。大页与缓存路径差异两条分配路径在地址来源、释放语义上不同大页池释放为空操作若在算子代码中自行管理Object派生对象需确保申请与释放经由同一套内部机制避免内存池状态不一致。总结Object是 CANN opbase 基础框架中一组定位明确、实现精巧的预留接口对外仅暴露四个文档化接口对内则通过operator new/operator delete重载将全部对象级与数组级分配汇聚到统一的Allocate/DeAllocate并依据线程本地上下文在 BlockCache 与 HugeMemPool 两条路径间分派最终以 16 字节对齐契约支撑aclTensor等核心对象的稳定创建。对算子开发者而言理解这条调用链的价值在于即使不直接使用这些预留接口也能在遇到对象内存相关异常时迅速定位到 object.cpp、bridge_pool.cpp 与 test_alignment.cpp 等关键实现与验证点。【免费下载链接】opbase本项目是CANN算子库的基础框架库为算子提供公共依赖文件和基础调度能力。项目地址: https://gitcode.com/cann/opbase创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表