内存管理
Ascend 310支持以下两种内存管理方式:原生语言的内存管理接口和Matrix框架提供的内存管理接口。
原生语言的内存管理接口
原生语言的内存管理接口包括malloc、free、memcpy、memset、new、delete等接口,支持C/C++等语言,由此类接口申请的内存,用户可以自行管理和控制内存使用的生命周期。用户申请内存空间小于256k时,使用原生语言的内存接口与Matrix框架提供的内存管理接口在性能上区别不大,基于简单便捷考虑,建议使用原生语言的内存管理接口。
// Use malloc to alloc buffer unsigned char* inbuf = (unsigned char*)malloc( fileLen ); // free buffer free(inbuf); inbuf = nullptr;
Matrix框架提供的内存管理接口
框架单独提供了一套内存分配和释放接口,包括HIAI_DMalloc/HIAI_DFree、 HIAI_DVPP_DMalloc/HIAI_DVPP_DFree,支持C/C++语言。其中,HIAI_DMalloc/HIAI_DFree接口主要用于申请内存,再配合SendData接口从Host侧搬运数据到Device侧;HIAI_DVPP_DMalloc/HIAI_DVPP_DFree接口主要用于申请Device侧DVPP使用的内存。通过调用HIAI_DMalloc/HIAI_DFree、 HIAI_DVPP_DMalloc/HIAI_DVPP_DFree接口申请内存,能够尽量少拷贝,减少流程处理时间。
接口描述
关于HIAI_DMalloc/HIAI_DFree、 HIAI_DVPP_DMalloc/HIAI_DVPP_DFree接口的功能,如表9-1所示。
接口名称 |
接口功能 |
---|---|
HIAIMemory::HIAI_DMalloc(C++专用接口) |
申请内存接口, 该内存类似普通内存,在用跨侧传输(Host-Device/Device-Host)以及模型推理处理时,性能更优。 |
HIAIMemory::HIAI_DFree(C++专用接口) |
与HIAIMemory::HIAI_DMalloc配合使用, 用于释放HIAIMemory::HIAI_DMalloc分配的内存。 如果在调用HIAIMemory::HIAI_DMalloc接口时,将flag参数值设置为MEMORY_ATTR_AUTO_FREE,表示如果分配了内存,且通过SendData接口发送数据到对端,则无需调用HIAIMemory::HIAI_DFree,对端接收到数据后,内存会自动释放;如果分配了内存,但没有通过SendData接口发送数据到对端或者通过SendData接口发送数据失败,则需要调用HIAIMemory::HIAI_DFree释放内存。 |
HIAI_DMalloc(C语言与C++通用接口) |
申请内存接口, 该内存类似普通内存,在用跨侧传输(Host-Device/Device-Host)以及模型推理处理时,性能更优。 |
HIAI_DFree(C语言与C++通用接口) |
与HIAI_DMalloc配合使用, 用于释放HIAI_DMalloc分配的内存。 如果在调用HIAI_DMalloc接口时,将flag参数值设置为MEMORY_ATTR_AUTO_FREE,表示如果分配了内存,且通过SendData接口发送数据到对端,则无需调用HIAI_DFree,程序运行结束后,内存会自动释放;如果分配了内存,但没有通过SendData接口发送数据到对端或者通过SendData接口发送数据失败,则需要调用HIAI_DFree释放内存。 |
HIAIMemory::HIAI_DVPP_DMalloc(C++专用接口) |
申请内存接口,该接口主要给Device端分配DVPP使用的内存。 |
HIAIMemory::HIAI_DVPP_DFree(C++专用接口) |
用于释放内存, 该接口主要用于释放HIAIMemory::HIAI_DVPP_DMalloc接口分配的内存。 |
HIAI_DVPP_DMalloc ( C语言与C++通用接口 ) |
申请内存接口,该接口主要给Device端分配DVPP使用的内存。 |
HIAI_DVPP_DFree ( C语言与C++通用接口) |
用于释放内存, 该接口主要用于释放HIAI_DVPP_DMalloc接口分配的内存。 |
接口调用流程
图9-2中关于接口使用的说明如下:
- 通过HIAI_DMalloc或HIAIMemory::HIAI_DMalloc接口申请的内存可用于端到端数据传输以及模型推理时使用。调用HIAI_DMalloc或HIAIMemory::HIAI_DMalloc接口,同时配合使用HIAI_REGISTER_SERIALIZE_FUNC宏(对用户自定义数据类型进行序列化或反序列化),可使数据传输效率更高,性能更优。
通过HIAI_DMalloc或HIAIMemory::HIAI_DMalloc接口申请内存,有以下优势:
- 申请的内存是可以直接给到HDC做数据搬运的, 这样可以避免Matrix与HDC间的数据拷贝。
- 申请的内存可以直接使能模型推理零拷贝机制,减少数据拷贝时间。
- 通过HIAI_DVPP_DMalloc或HIAIMemory::HIAI_DVPP_DMalloc接口申请的内存可以给DVPP使用, 同时也可以在DVPP使用完后透传给模型推理时使用。如果不需要做模型推理,HIAI_DVPP_DMalloc接口申请的内存中的数据可以直接回传给Host侧。
- 通过HIAI_DMalloc、HIAIMemory::HIAI_DMalloc、HIAI_DVPP_DMalloc、HIAIMemory::HIAI_DVPP_DMalloc接口申请的内存兼容原生语言的内存管理接口, 可以当做普通内存使用,但是不能使用free、delete等来释放。一般来说,通过HIAI_DMalloc、HIAIMemory::HIAI_DMalloc、HIAI_DVPP_DMalloc、HIAIMemory::HIAI_DVPP_DMalloc接口申请的内存,需要分别通过调用HIAI_DFree、HIAIMemory::HIAI_DFree、HIAI_DVPP_DFree、HIAIMemory::HIAI_DVPP_DFree接口释放内存。
如果在调用HIAI_DMalloc或HIAIMemory::HIAI_DMalloc接口时,将flag参数值设置为MEMORY_ATTR_AUTO_FREE,且通过SendData接口发送数据到对端,则无需调用HIAI_DFree或HIAIMemory::HIAI_DFree接口,对端接收到数据后,内存会自动释放;如果仅将flag参数值设置为MEMORY_ATTR_AUTO_FREE,但没有通过SendData接口发送数据到对端或者通过SendData接口发送数据失败,则需要调用HIAIMemory::HIAI_DFree释放内存。
- 通过HIAI_DVPP_DMalloc、HIAIMemory::HIAI_DVPP_DMalloc接口申请的内存是满足DVPP要求的内存,所以资源较为有限,建议仅当DVPP处理场景时使用。
接口使用要点
通过HIAI_DMalloc或HIAIMemory::HIAI_DMalloc接口申请内存,关于内存管理,请注意以下要点:
- 申请自动释放内存,用于Host到Device或Device到Host的数据传输时,如果是智能指针,由于Matrix框架自动释放内存,所以智能指针指定的析构器必须是空的;如果非智能指针,则Matrix框架自动释放。
- 申请手动释放内存,用于Host到Device或Device到Host的数据传输时,如果是智能指针,则需要指定析构器为HIAI_DFree或HIAIMemory::HIAI_DFree;如果非智能指针,则数据发送完成后需要调用HIAI_DFree或HIAIMemory::HIAI_DFree释放内存。
- 申请自动释放内存,对于该内存中的数据,不允许多次调用SendData接口发送数据。
- 申请手动释放内存时,如果用于Host到Device或Device到Host的数据传输时,在内存释放前,不可复用内存中的数据;如果用于Host到Host或Device到Device的数据传输时,在内存释放前,可以复用内存中的数据。
- 申请手动释放内存时,如果调用SendData接口异步传输数据,发送数据后,不允许修改内存中的数据。
如果调用HIAI_DVPP_MAlloc或HIAIMemory::HIAI_DVPP_DMalloc接口申请内存,用于Device到Host的数据传输时,由于HIAI_DVPP_MAlloc或HIAIMemory::HIAI_DVPP_DMalloc没有自动释放标签,所以一定需要调用HIAI_DVPP_DFree或HIAIMemory::HIAI_DVPP_DFree接口手动释放内存。如果使用智能指针存放申请的内存地址, 必须指定析构器为HIAI_DVPP_DFree或HIAIMemory::HIAI_DVPP_DFree。
接口调用示例
(1)使用性能优化方案传输数据,必须对发送数据的接口进行手动序列化和反序列化: // 注:序列化函数在发送端使用,反序列化在接收端使用,所以这个注册函数最好在接收端和发送端都注册一遍; //数据结构 typedef struct { uint32_t left_offset = 0; uint32_t right_offset = 0; uint32_t top_offset = 0; uint32_t bottom_offset = 0; //下面serialize函数用于序列化结构体 template <class Archive> void serialize(Archive & ar) { ar(left_offset,right_offset,top_offset,bottom_offset); } } crop_rect; // 注册Engine将流转的结构体 typedef struct EngineTransNew { std::shared_ptr<uint8_t> trans_buff = nullptr; // 传输Buffer uint32_t buffer_size = 0; // 传输Buffer大小 std::shared_ptr<uint8_t> trans_buff_extend = nullptr; uint32_t buffer_size_extend = 0; std::vector<crop_rect> crop_list; //下面serialize函数用于序列化结构体 template <class Archive> void serialize(Archive & ar) { ar(buffer_size, buffer_size_extend, crop_list); } }EngineTransNewT; //序列化函数 /** * @ingroup hiaiengine * @brief GetTransSearPtr, 序列化Trans数据 * @param [in] : data_ptr 结构体指针 * @param [out]:struct_str 结构体buffer * @param [out]:data_ptr 结构体数据指针buffer * @param [out]:struct_size 结构体大小 * @param [out]:data_size 结构体数据大小 */ void GetTransSearPtr(void* data_ptr, std::string& struct_str, uint8_t*& buffer, uint32_t& buffer_size) { EngineTransNewT* engine_trans = (EngineTransNewT*)data_ptr; uint32_t dataLen = engine_trans->buffer_size; uint32_t dataLen_extend = engine_trans->buffer_size_extend; // 获取结构体buffer和size buffer_size = dataLen + dataLen_extend; buffer = (uint8_t*)engine_trans->trans_buff.get(); // 序列化处理 std::ostringstream outputStr; cereal::PortableBinaryOutputArchive archive(outputStr); archive((*engine_trans)); struct_str = outputStr.str(); } //反序列化函数 /** * @ingroup hiaiengine * @brief GetTransSearPtr, 反序列化Trans数据 * @param [in] : ctrl_ptr 结构体指针 * @param [in] : data_ptr 结构体数据指针 * @param [out]:std::shared_ptr<void> 传给Engine的指针结构体指针 */ std::shared_ptr<void> GetTransDearPtr( const char* ctrlPtr, const uint32_t& ctrlLen, const uint8_t* dataPtr, const uint32_t& dataLen) { if(ctrlPtr == nullptr) { return nullptr; } std::shared_ptr<EngineTransNewT> engine_trans_ptr = std::make_shared<EngineTransNewT>(); // 给engine_trans_ptr赋值 std::istringstream inputStream(std::string(ctrlPtr, ctrlLen)); cereal::PortableBinaryInputArchive archive(inputStream); archive((*engine_trans_ptr)); uint32_t offsetLen = engine_trans_ptr->buffer_size; if(dataPtr != nullptr) { (engine_trans_ptr->trans_buff).reset((const_cast<uint8_t*>(dataPtr)), ReleaseDataBuffer); // 因为trans_buff和trans_buff_extend指向的是一块以dataPtr为首地址的连续内存空间, // 因此只需要trans_buff挂载析构器释放一次即可 (engine_trans_ptr->trans_buff_extend).reset((const_cast<uint8_t*>(dataPtr + offsetLen)), SearDeleteNothing); } return std::static_pointer_cast<void>(engine_trans_ptr); } // 注册EngineTransNewT HIAI_REGISTER_SERIALIZE_FUNC("EngineTransNewT", EngineTransNewT, GetTransSearPtr, GetTransDearPtr); (2) 在发送数据时,需要使用注册的数据类型,另外配合使用HIAI_DMalloc分配数据内存,可以使性能更优 注:在从host侧向Device侧搬运数据时,使用HIAI_DMalloc方式将很大的提供传输效率,建议优先使用HIAI_DMalloc,该内存接口目前支持0 – (256M Bytes - 96 Bytes)的数据大小,如果数据超出该范围,则需要使用malloc接口进行分配; // 使用Dmalloc接口申请数据内存,10000为时延,为10000毫秒,表示如果内存不足,等待10000毫秒; HIAI_StatusT get_ret = HIAIMemory::HIAI_DMalloc(width*align_height*3/2,(void*&)align_buffer, 10000); // 发送数据,调用该接口后无需调用HIAI_DFree接口,10000为时延 graph->SendData(engine_id_0, "TEST_STR", std::static_pointer_cast<void>(align_buffer), 10000);