【TensorRT】c++使用面向对象来封装tensorRT推理代码的指针释放问题

发布时间:2024年01月18日

使用类来封装智能指针创建的tensorRT推理engine,runtime,context

一、🍎代码框架🍎

初始化模型

std::shared_ptr<nvinfer1::IExecutionContext> Instance::Init_Instance(const char* model_path, const string class_name_path)
{
    //注册防止反序列化报错
    nvinfer1::ILogger* gLogger = NULL;
    initLibNvInferPlugins(gLogger, "");

    TRTLogger logger;
    ifstream fs(model_path, std::ios::binary);
    std::vector<unsigned char> buffer(std::istreambuf_iterator<char>(fs), {});
    fs.close();
    std::shared_ptr<nvinfer1::IRuntime> runtime = make_nvshared(nvinfer1::createInferRuntime(logger));
    //nvinfer1::IRuntime *runtime = nvinfer1::createInferRuntime(logger);

    _engine = make_nvshared(runtime->deserializeCudaEngine((void*)buffer.data(), buffer.size()));
    //_engine = runtime->deserializeCudaEngine((void*)buffer.data(), buffer.size());

    Malloc_data();
    class_names = get_name(class_name_path);

    //初始化cuda流,不需要了,在类中已经作为成员变量初始化了
    //cudaStream_t stream = nullptr;
    checkRuntime(cudaStreamCreate(&stream));
    //创建执行的上下文
    auto execution_context = make_nvshared(_engine->createExecutionContext());
    return execution_context;
}

这里可以看到我用了智能指针来分别定义_engine, _runtime, _context。并且我在头文件中定义了一个类来封装我的推理代码,包括初始化模型的步骤:


类封装

class Instance {
public:
    Instance(float scale_):scale(scale_)
    {
        cout << "调用了构造函数" << endl;
    }
    ~Instance() {
        cout << "调用了析构函数" << endl;
    };
    float scale;
    std::shared_ptr < nvinfer1::IExecutionContext > _context = nullptr;
    //nvinfer1::IExecutionContext* _context = nullptr;

    vector<string> class_names;
    string total_output;

	std::shared_ptr<nvinfer1::IExecutionContext> Init_Instance(const char* model_path, const string class_name_path);
    //nvinfer1::IExecutionContext* Init_Instance(const char* model_path, const string class_name_path);

	void Run_Instance(cv::Mat input);
    void check_ptr();
    void Free_Memory();
    vector<string> get_name(string class_name_path);
    void destory_engine();

private:

    void Malloc_data();
    cv::cuda::GpuMat ProcessImage(cv::cuda::GpuMat pre_input);
    void PostcessImage(cv::Mat nums, cv::Mat boxes, cv::Mat scores, cv::Mat classes, cv::Mat masks);
    //void Init_parameters();

    int INPUT_SIZE = 1344;
    int MIN_SIZE = 800;
    int MAX_SIZE = 1333;
    int MASK_SIZE = 28;
    float CONFIDENCE = 0;
    float MASK_THRESHOLD = 0.5;
    int input_numel = 1;
    int output_nums_numel = 1;
    int output_boxes_numel = 1;
    int output_scores_numel = 1;
    int output_classes_numel = 1;
    int output_masks_numel = 1;
    cv::Mat INPUT;

    cudaStream_t stream = nullptr;
    std::shared_ptr<nvinfer1::ICudaEngine> _engine = nullptr;
    std::shared_ptr<nvinfer1::IRuntime> _runtime = nullptr;
    //nvinfer1::ICudaEngine* _engine = nullptr;
    //nvinfer1::IRuntime* _runtime = nullptr;

  
    //初始化输入和输出指针
    //static float* input_host_data = nullptr;
    float* input_device_data = nullptr;
    Output_ptr output_host = { nullptr, nullptr, nullptr, nullptr, nullptr };
    Output_ptr output_device = { nullptr, nullptr, nullptr, nullptr, nullptr };
};

在这里可以看到,我已经将_engine, _runtime, _context都定义在了类的成员变量当中,并且都用的智能指针shared_ptr的方式。(并且我也在类中封装了Malloc_data()和Free_malloc()函数,作用分别是为tensorRT推理时在host和device上为输入输出的指针分配存储空间,和在执行推理完毕后,将host和device上分配的指针指向的内存空间手动释放掉)。


主函数
第一次封装是将我的推理代码封装在类中,为了方便c#软件调用部署导出的dll,我们将进行二次封装,因此主函数的接口使用的是二次封装的:

#include "port.h"

int main()
{
	cv::String folder = ;
	std::vector<cv::String> paths;
	cv::glob(folder, paths);
	const char* model_path = ;
	const string class_path = ;
	Init_model(model_path, class_path);
	for (int i = 0; i < paths.size(); i++)
	{
		char* y_output = NULL;
		cv::Mat input = cv::imread(paths[i]);
		cv::Mat bgr[3];
		cv::split(input, bgr);
		uchar* b_ptr = bgr[0].data;
		uchar* g_ptr = bgr[1].data;
		uchar* r_ptr = bgr[2].data;
		auto time_start = std::chrono::system_clock::now();
		Run_model(b_ptr, g_ptr, r_ptr, 800, 800, 3, y_output);
		auto time_end = std::chrono::system_clock::now();
		std::chrono::duration<double> diff = time_end - time_start;
		cout << y_output;
		cout << "deep learning cost time : " << diff.count() * 1000 << "ms" << endl << endl;
	}
	Free_memory();
	check_Ptr();
	destory_trt();
	system("pause");
	return 0;
}

可以看到在上述代码经过一个for循环遍历完所有要检测的图像之后,在循环外部,程序即将结束之前,我们调用了Free_memory()来手动释放host和device指针指向的内存以及释放推理时创建的cuda流,并且通过Check_ptr()方法来判断指针是否被释放完成。最后我们通过destory_trt()接口来释放推理时创建的_engine, _runtime, _context。最后跑出的推理结果十分正确,代码跑的也很流畅没有bug,并且通过一个写了一个while死循环来跑也没有发现内存泄露。到这里我以为我已经大功告成了。


二、💡问题以及分析💡

在这里插入图片描述
这是终端跑出来的结果,可以看到执行完了Free_memory()并且通过check_ptr()方法,我们可以判断指针已经释放完毕了。但是当我们经过system(“pause”)继续往下执行推出程序时,问题就来了:
在这里插入图片描述
是NvInferRuntime.h所报出的问题,显示我们可管理的指针将被执行两次释放,说人话就是我们的指针已经释放完了,但是现在又要执行那个指针的释放,电脑也不知道咋办了,只能抛出错误。于是乎,我便开始从头开始查询我的代码,看看到底哪里多释放了一次,后来突然发现,由于我使用了智能指针,智能指针在其完成调用之后,根据引用计数可以自动释放,而我的这些_engine, _context, _runtime,也都已经在类的成员变量中定义过了,在类中如果不是new出来的内存,其他的都会随着程序的执行结束而自动销毁(new出来的内存需要手动delete)。到这里我才恍然大悟,既然我用类来封装代码了,因此也不需要通过智能指针的方式来定义推理的变量了,因此我将这三个智能指针换成了普通指针,最后运行,没有问题!
在这里插入图片描述

文章来源:https://blog.csdn.net/ycx_ccc/article/details/135672498
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。