C++内存池Memory Pool的高级实现、代码详解、CMake构建工程、应用实例

目录

1 文件组成

1.1 buf_surface_util.cpp----上层用户文件

1.2 buf_surface_utils.cpp

1.3 buf_surface.cpp----中间接口文件

1.4 buf_surface_impl.cpp----底层软件层面实施文件

1.5 buf_surface_impl_device.cpp和buf_surface_impl_system.cpp----硬件层面实施文件

2 代码架构与流程

3 怎么把某个surface回收push回内存池----使用智能指针

3.1 情况1----surf_不会被BufSurfaceWrapper的析构函数回收

3.2 情况2----surf_会被BufSurfaceWrapper的析构函数回收

4 怎么销毁整个内存池

5 cmake构建工程

6 应用实例

6.1 app.h

6.2 app.cpp

6.3 main.cpp主程序

参考文献:


这是从 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博客

猜你喜欢

转载自blog.csdn.net/u013171226/article/details/142184528