目录
1.1 buf_surface_util.cpp----上层用户文件
1.4 buf_surface_impl.cpp----底层软件层面实施文件
1.5 buf_surface_impl_device.cpp和buf_surface_impl_system.cpp----硬件层面实施文件
3 怎么把某个surface回收push回内存池----使用智能指针
3.1 情况1----surf_不会被BufSurfaceWrapper的析构函数回收
3.2 情况2----surf_会被BufSurfaceWrapper的析构函数回收
这是从 CNStream 中整理出来的内存池代码,然后介绍了他的文件组成、代码流程、cmake构建以及使用demo,整理后的代码上传到了GitHub,https://github.com/cumtchw/MemoryPool
1 文件组成
1.1 buf_surface_util.cpp----上层用户文件
这个算是最上层的文件,这个文件里面有两个类,一个类是BufPool,这个类是用户使用的用来创建内存池。还有一个类是BufSurfaceWrapper,从类的名字wrapper就能知道这是个包装类,这个类BufSurfaceWrapper相当于是给BufSurface封装了一些操作接口,可以通过接口直接获取surface的一些变量或者参数,用户通过这个类的接口操作BufSurface。
用户真正使用时会继承一个IUserPool 类,
class IUserPool {
public:
virtual ~IUserPool() {}
virtual void OnBufInfo(int width, int height, BufSurfaceColorFormat fmt) = 0;
virtual int CreatePool(BufSurfaceCreateParams *params, uint32_t block_count) = 0;
virtual void DestroyPool() = 0;
virtual infer_server::BufSurfWrapperPtr GetBufSurface(int timeout_ms) = 0;
};
这个类里面可以看到就是创建内存池以及获取内存池的BufSurfaceWrapper,然后我们用户类除了继承这个IUserPool以外,一般来说用户类里面还会有一个BufPool pool_成员,这样就通过IUserPool的接口以及Bufpool成员去创建以及使用内存池,
比如下面的FileHandlerImpl 就是我们的一个用户类。
class FileHandlerImpl : public IUserPool {//公有继承IUserPool
public:
...构造函数以及其他函数...
private:
...其他函数成员...
// 重载IUserPool的函数
int CreatePool(BufSurfaceCreateParams *params, uint32_t block_count);
void DestroyPool() override;
void OnBufInfo(int width, int height, BufSurfaceColorFormat fmt);
BufSurfWrapperPtr GetBufSurface(int timeout_ms) override;
private:
BufPool pool_;
...其他成员...
};
1.2 buf_surface_utils.cpp
这个文件里面就只有两个辅助函数GetColorFormatDataSize和CheckParams,不需要过多关注。
1.3 buf_surface.cpp----中间接口文件
buf_surface.cpp里面就是用单例模式实现的一个中间类BufSurfaceService,然后这个类里面主要就是BufPoolCreate、BufPoolDestroy、BufSurfaceCreate、BufSurfaceDestroy这种,然后这个文件的最后面用几个C接口去调用BufSurfaceService的接口。
buf_surface.h定义了各种结构体BufSurfaceColorFormat、BufSurfaceMemType、BufSurfaceCreateParams、BufSurfaceParams。
1.4 buf_surface_impl.cpp----底层软件层面实施文件
这里面有一个类MemPool,这个类其实就是具体的内存池类了,这个类里面的成员std::queue<BufSurface> cache_;就是保存的内存池。MemPool虽然是具体的内存池类了,但是他还没到硬件的程度,也就是这个内存池是在cpu上的还是在比如GPU上的,所以他下面还需要有一个可以称为硬件实施类的底层类,所以MemPool还有一个成员IMemAllcator *allocator_ = nullptr用来保存底层硬件实施类的指针的。
然后buf_surface_impl.cpp里面还有一个虚基类IMemAllcator,这个就是用来被底层具体硬件的内存池类MemAllocatorDevice 或者MemAllocatorSystem 继承的。
class IMemAllcator {
public:
virtual ~IMemAllcator() {}
virtual int Create(BufSurfaceCreateParams *params) = 0;
virtual int Destroy() = 0;
virtual int Alloc(BufSurface *surf) = 0;
virtual int Free(BufSurface *surf) = 0;
};
1.5 buf_surface_impl_device.cpp和buf_surface_impl_system.cpp----硬件层面实施文件
buf_surface_impl_device.cpp,这个文件主要是做设备内存的申请和释放的,里面的类是class MemAllocatorDevice : public IMemAllcator 负责具体的申请和释放内存,这个算是最底层实施类。
buf_surface_impl_system.cpp,这个文件主要是做主机内存的申请和释放的,里面的类是class MemAllocatorSystem : public IMemAllcator。负责具体的申请和释放内存,这个算是最底层实施类。
这两个类要继承前面说的IMemAllcator ,然后作为MemPool类的一个成员。
2 代码架构与流程
上面从文件组成已经能基本上看出来代码流程了,下面简单的画一个图看一下流程。鼠标右键--另存为可以保存然后放大查看图片。
3 怎么把某个surface回收push回内存池----使用智能指针
首先用户一般使用GetBufSurfaceWrapper函数去获取surface的包装接口,
BufSurfWrapperPtr FileHandlerImpl::GetBufSurface(int timeout_ms) {
if (pool_created_) {
std::unique_lock<std::mutex> lk(mutex_);
return pool_.GetBufSurfaceWrapper(timeout_ms);
}
if (param_.bufpool_size > 0) {
LOGI(SOURCE) << "[FileHandlerImpl] GetBufSurface(): Create pool";
if (CreatePool(&create_params_, param_.bufpool_size) < 0) {
LOGE(SOURCE) << "[FileHandlerImpl] GetBufSurface(): Create pool failed";
return nullptr;
}
std::unique_lock<std::mutex> lk(mutex_);
return pool_.GetBufSurfaceWrapper(timeout_ms);
}
BufSurface *surf = nullptr;
if (BufSurfaceCreate(&surf, &create_params_) < 0) {
LOGE(SOURCE) << "[FileHandlerImpl] GetBufSurface() Create BufSurface failed.";
return nullptr;
}
return std::make_shared<BufSurfaceWrapper>(surf);
}
然后可以看到GetBufSurfaceWrapper返回的是智能指针std::shared_ptr<BufSurfaceWrapper>;
BufSurfWrapperPtr BufPool::GetBufSurfaceWrapper(int timeout_ms) {
std::unique_lock<std::mutex> lk(mutex_);
if (!pool_) {
LOG(ERROR) << "[InferServer] [BufPool] GetBufSurfaceWrapper(): Pool is not created";
return nullptr;
}
BufSurface *surf = nullptr;
int count = timeout_ms + 1;
int retry_cnt = 1;
while (1) {
if (stopped_) {
// Destroy called, disable alloc-new-block
LOG(ERROR) << "[InferServer] [BufPool] GetBufSurfaceWrapper(): Pool is stopped";
return nullptr;
}
int ret = BufSurfaceCreateFromPool(&surf, pool_);
if (ret == 0) {
return std::make_shared<BufSurfaceWrapper>(surf);
}
count -= retry_cnt;
VLOG(3) << "[InferServer] [BufPool] GetBufSurfaceWrapper(): retry, remaining times: " << count;
if (count <= 0) {
LOG(ERROR) << "[InferServer] [BufPool] GetBufSurfaceWrapper(): Maximum number of attempts reached: "
<< timeout_ms;
return nullptr;
}
lk.unlock();
usleep(1000 * retry_cnt);
retry_cnt = std::min(retry_cnt * 2, 10);
lk.lock();
}
return nullptr;
}
那么当这个智能指针没有被引用的时候就会被销毁,然后就会调用BufSurfaceWrapper的析构函数
~BufSurfaceWrapper() {
std::unique_lock<std::mutex> lk(mutex_);
if (deleter_) {
delete deleter_, deleter_ = nullptr;
return;
}
if (owner_ && surf_) BufSurfaceDestroy(surf_), surf_ = nullptr;
}
然后在析构函数里面如果owner为true,那么就会调用BufSurfaceDestroy
最终调用到
int MemPool::Free(BufSurface *surf) {
std::unique_lock<std::mutex> lk(mutex_);
if (!created_) {
LOG(ERROR) << "[InferServer] [MemPool] Free(): Memory pool is not created";
return -1;
}
cache_.push(*surf);
--alloc_count_;
return 0;
}
虽然是用了智能指针去管理BufSurfaceWrapper,但是也不是说BufSurfaceWrapper的析构函数里面就一定会用BufSurfaceDestroy(surf_)回收surf,我们可以看到
if (owner_ && surf_) BufSurfaceDestroy(surf_), surf_ = nullptr;
代码中有个对surf_的判断,所以存在下面两种情况
3.1 情况1----surf_不会被BufSurfaceWrapper的析构函数回收
BufSurface *BufSurfaceWrapper::BufSurfaceChown() {
std::unique_lock<std::mutex> lk(mutex_);
BufSurface *surf = surf_;
surf_ = nullptr;
return surf;
}
如果我们用上面接口获取surf,比如
BufSurface* surf;
if (wrapper) {
surf = wrapper->BufSurfaceChown();
}
那么因为这个接口里面有个 surf_ = nullptr;所以BufSurfaceWrapper的析构函数就不会回收这个surf,
3.2 情况2----surf_会被BufSurfaceWrapper的析构函数回收
BufSurface *BufSurfaceWrapper::GetBufSurface() const {
std::unique_lock<std::mutex> lk(mutex_);
return surf_;
}
如果我们用上面的接口获取surf,比如
if (wrapper2) {
surf2 = wrapper2->GetBufSurface();
}
那么BufSurfaceWrapper的析构函数就会自动回收surf_。
4 怎么销毁整个内存池
在我们用户类的比如close函数里面会调用DestroyPool去一层层销毁内存池。
void FileHandlerImpl::Close() {
Stop();
LOGI(SOURCE) << "[FileHandlerImpl] Close(): this(" << this << ") Destroy pool";
DestroyPool();
}
5 cmake构建工程
使用时,直接执行./build.sh即可,以上已经在NVIDIA Jetson Orin上编译运行通过,jetpack版本为5.1.3,cuda11.4。
6 应用实例
上面的代码原本是https://github.com/Cambricon/CNStream 中的内存池相关的代码,我先是把整个的CNStream移植到了NVIDIA Jetson Orin上面,然后又把里面的内存池相关代码单独摘出来,至于案例,我就用个opencv做一下图片转换,然后里面用一下这个内存池代码,只是为了演示一下内存池的使用,
6.1 app.h
#ifndef __APP_H__
#define __APP_H__
#include <map>
#include <memory>
#include <mutex>
#include <string>
#include <utility>
#include <vector>
#include "buf_surface_util.hpp"
using namespace infer_server;
class IUserPool {
public:
virtual ~IUserPool() {}
virtual void OnBufInfo(int width, int height, BufSurfaceColorFormat fmt) = 0;
virtual int CreatePool(BufSurfaceCreateParams *params, uint32_t block_count) = 0;
virtual void DestroyPool() = 0;
virtual infer_server::BufSurfWrapperPtr GetBufSurface(int timeout_ms) = 0;
};
/*!
* @struct DataSourceParam
*
* @brief The DataSourceParam is a structure describing the parameters of a DataSource module.
*/
struct DataSourceParam {
uint32_t interval = 1; /*!< The interval of outputting one frame. It outputs one frame every n (interval_) frames. */
int device_id = 0; /*!< The device ordinal. */
uint32_t bufpool_size = 16; /*!< The size of the buffer pool to store output frames. */
};
class Transform : public IUserPool {
public:
explicit Transform() {}
~Transform()
{
DestroyPool();
}
private:
DataSourceParam param_;
BufSurfaceCreateParams create_params_;
public:
// IUserPool
int CreatePool(BufSurfaceCreateParams *params, uint32_t block_count);
void DestroyPool() override;
void OnBufInfo(int width, int height, BufSurfaceColorFormat fmt);
BufSurfWrapperPtr GetBufSurface(int timeout_ms) override;
private:
private:
BufPool pool_;
bool pool_created_ = false;
std::mutex mutex_;
}; // class
#endif
6.2 app.cpp
#include <string>
#include <chrono>
#include <memory>
#include <sstream>
#include <string>
#include <thread>
#include <utility>
#include "app.h"
#include "opencv2/opencv.hpp"
#include "cnstream_logging.hpp"
//using namespace infer_server;
int Transform::CreatePool(BufSurfaceCreateParams *params, uint32_t block_count) {
std::unique_lock<std::mutex> lk(mutex_);
if (!pool_.CreatePool(params, block_count)) {
pool_created_ = true;
return 0;
}
LOGE(SOURCE) << "[Transform] CreatePool(): Create pool failed.";
return -1;
}
void Transform::DestroyPool() {
std::unique_lock<std::mutex> lk(mutex_);
pool_.DestroyPool(5000);
}
void Transform::OnBufInfo(int width, int height, BufSurfaceColorFormat fmt) {
memset(&create_params_, 0, sizeof(BufSurfaceCreateParams));
create_params_.width = width;
create_params_.height = height;
create_params_.device_id = param_.device_id;
create_params_.batch_size = 1;
create_params_.color_format = fmt;
create_params_.mem_type = BUF_MEMORY_MAP;
return;
}
BufSurfWrapperPtr Transform::GetBufSurface(int timeout_ms) {
if (pool_created_) {
std::unique_lock<std::mutex> lk(mutex_);
return pool_.GetBufSurfaceWrapper(timeout_ms);
}
if (param_.bufpool_size > 0) {
LOGI(SOURCE) << "[Transform] GetBufSurface(): Create pool";
if (CreatePool(&create_params_, param_.bufpool_size) < 0) {
LOGE(SOURCE) << "[Transform] GetBufSurface(): Create pool failed";
return nullptr;
}
std::unique_lock<std::mutex> lk(mutex_);
return pool_.GetBufSurfaceWrapper(timeout_ms);
}
BufSurface *surf = nullptr;
if (BufSurfaceCreate(&surf, &create_params_) < 0) {
LOGE(SOURCE) << "[Transform] GetBufSurface() Create BufSurface failed.";
return nullptr;
}
return std::make_shared<BufSurfaceWrapper>(surf);
}
6.3 main.cpp主程序
这个代码写的并不合理,只是为了演示下内存池的使用。
#include <iostream>
#include <fstream>
#include <vector>
#include <opencv2/opencv.hpp>
#include "app.h"
int main(int argc, char** argv) {
const char* filename = "1920.nv12";
const int width = 1920;
const int height = 1080;
const int frameSize = width * height * 3 / 2; // NV12 format: Y plane + UV plane
std::ifstream file(filename, std::ios::binary);
if (!file) {
std::cerr << "无法打开文件: " << filename << std::endl;
return -1;
}
std::vector<uint8_t> buffer(frameSize);
file.read(reinterpret_cast<char*>(buffer.data()), frameSize);
if (!file) {
std::cerr << "读取文件失败: " << filename << std::endl;
return -1;
}
file.close();
std::shared_ptr<Transform> transform_ptr = std::make_shared<Transform>();
transform_ptr->OnBufInfo(width, height, BUF_COLOR_FORMAT_BGR);
BufSurfWrapperPtr wrapper = transform_ptr->GetBufSurface(1000);
BufSurface* surf;
/*************************************************************************************************
这里用BufSurfaceChown接口,那么BufSurfWrapperPtr的析构函数里面不会销毁surf,因为BufSurfaceChown函数内部把
surf_ = nullptr;而析构函数内部有个判断if (owner_ && surf_) BufSurfaceDestroy(surf_), surf_ = nullptr;也
就是当surf_不是null才会BufSurfaceDestroy(surf_)。
***************************************************************************************************/
if (wrapper) {
surf = wrapper->BufSurfaceChown();
}
cv::Mat bgrImg(height, width, CV_8UC3, surf->surface_list[0].data_ptr);
cv::Mat yuvImg(height + height / 2, width, CV_8UC1, buffer.data());
cv::cvtColor(yuvImg, bgrImg, cv::COLOR_YUV2BGR_NV12);
//然后把这个surf进一步传给后续模块使用。然后这样surf又让智能指针BufSurfWrapperPtr给管理了,不需要我们手动的destroy。
BufSurfWrapperPtr wrapper2 = std::make_shared<BufSurfaceWrapper>(surf);
//实际场景可能是另一个模块用wrapper2去对图片做进一步处理。 这里使用 wrapper2 中的数据保存为 JPG 文件简单模拟下实际场景。
BufSurface* surf2;
/*************************************************************************************************
这里用GetBufSurface接口,那么BufSurfWrapperPtr的析构函数里面就会销毁surf,因为析构函数内部有代码
if (owner_ && surf_) BufSurfaceDestroy(surf_), surf_ = nullptr;
***************************************************************************************************/
if (wrapper2) {
surf2 = wrapper2->GetBufSurface();
}
cv::Mat finalImg(surf2->surface_list[0].height, surf2->surface_list[0].width, CV_8UC3, surf2->surface_list[0].data_ptr);
if (!cv::imwrite("output.jpg", finalImg)) {
std::cerr << "保存 JPG 文件失败" << std::endl;
return -1;
}
std::cout << "成功读取 NV12 文件并保存为 JPG 文件。" << std::endl;
return 0;
}
另外根目录下的.gitattributes文件备份
执行这三条
git lfs track "3rdparty/jetson/compute/lib/aarch64/libcublas.so.11.6.6.84"
git lfs track "3rdparty/jetson/compute/lib/aarch64/libcublasLt.so.11.6.6.84"
git lfs track "3rdparty/jetson/compute/lib/aarch64/libcudnn_cnn_infer.so.8.6.0"
然后.gitattributes文件中就会有
3rdparty/jetson/compute/lib/aarch64/libcublas.so.11.6.6.84 filter=lfs diff=lfs merge=lfs -text
3rdparty/jetson/compute/lib/aarch64/libcublasLt.so.11.6.6.84 filter=lfs diff=lfs merge=lfs -text
3rdparty/jetson/compute/lib/aarch64/libcudnn_cnn_infer.so.8.6.0 filter=lfs diff=lfs merge=lfs -text
参考文献:
https://github.com/Cambricon/CNStream
aclStream流处理多路并发Pipeline框架中 视频解码 代码调用流程整理、类的层次关系整理、回调函数赋值和调用流程整理-CSDN博客