7.5.tensorRT高级(2)-RAII接口模式下的生产者消费者多batch实现

目录

    • 前言
    • 1. RAII接口模式封装生产者消费者
    • 2. 问答环节
    • 总结

前言

杜老师推出的 tensorRT从零起步高性能部署 课程,之前有看过一遍,但是没有做笔记,很多东西也忘了。这次重新撸一遍,顺便记记笔记。

本次课程学习 tensorRT 高级-RAII 接口模式下的生产者消费者多 batch 实现

课程大纲可看下面的思维导图

在这里插入图片描述

1. RAII接口模式封装生产者消费者

这节课我们利用上节课学到的 RAII + 接口模式对我们的消费者生产者进行封装

我们来看代码

infer.hpp

#ifndef INFER_HPP
#define INFER_HPP#include <memory>
#include <string>
#include <future>class InferInterface{
public:virtual std::shared_future<std::string> forward(std::string pic) = 0;
};std::shared_ptr<InferInterface> create_infer(const std::string& file);#endif // INFER_HPP

infer.cpp

#include "infer.hpp"
#include <thread>
#include <queue>
#include <mutex>
#include <future>using namespace std;struct Job{shared_ptr<promise<string>> pro;string input;
};class InferImpl : public InferInterface{
public:virtual ~InferImpl(){worker_running_ = false;cv_notify_one();if(worker_thread_.joinable())worker_thread_.join();}bool load_model(const string& file){// 尽量保证资源哪里分配哪里释放,哪里使用,这样使得程序足够简单,而不是太乱// 线程内传回返回值的问题promise<bool> pro;worker_running_ = true;worker_thread_ = thread(&InferImpl::worker, this, file, std::ref(pro));return pro.get_future().get();}virtual shared_future<string> forward(string pic) override{// printf("使用 %s 进行推理\n", context_.c_str());// 往队列抛任务Job job;job.pro.reset(new promise<string>());job.input = pic;lock_guard<mutex> l(job_lock_);qjobs_.push(job);// 被动通知,一旦有新的任务需要推理,通知我即可// 发生通知的家伙cv_.notify_one();return job.pro->get_future();}// 实际执行模型推理的部分void worker(string file, promise<bool>& pro){// worker内实现,模型的加载,使用,释放string context = file;if(context.empty()){pro.set_value(false);return;}else{pro.set_value(true);}int max_batch_size = 5;vector<Job> jobs;int batch_id = 0;while(worker_running_){// 等待接受的家伙// 在队列取任务并执行的过程unique_lock<mutex> l(job_lock_);cv_.wait(job_lock_, [&](){// true 退出等待// false 继续等待return !qjobs_.empty() || !worker_running_;});// 程序发送终止信号if(!worker_running_)break;while(jobs.size() < max_batch_size && !qjobs_.empty()){jobs.emplace_back(qjobs_.front());qjobs.pop();}// 可以在这里一次拿一批出来,最大拿 maxbatchsize 个 job 进行一次性处理// jobs inference -> batch inference// 执行 batch 推理for(int i = 0; i < jobs.size(); ++i){auto& job = jobs[i];char result[100];sprintf(result, "%s : batch-> %d[%d]", job.input.c_str(), batch_id, jobs.size());job.pro->set_value(result);}batch_id++;jobs.clear();// 模拟推理耗时this_thread::sleep_for(chrono::milliseconds(1000));}// 释放模型printf("释放: %s\n", context.c_str());context.clear();printf("Worker done.\n");}
private:atomic<bool> worker_running_{false};thread worker_thread_;queue<Job> qjobs_;mutex job_lock_;condition_variable cv_;
};shared_ptr<InferInterface> create_infer(const string& file){shared_ptr<InferImpl> instance(new Infer());if(!instance->load_model(file))instance.reset();return instance;
}

main.cpp

#include "infer.hpp"int main(){auto infer = create_infer("a");if(infer == nullptr){printf("failed.\n");return -1;}// 串行// auto fa = infer->forward("A").get();// auto fb = infer->forward("B").get();// auto fc = infer->forward("C").get();// printf("%s\n", fa.c_str());// printf("%s\n", fb.c_str());// printf("%s\n", fc.c_str());// 并行auto fa = infer->forward("A");auto fb = infer->forward("B");auto fc = infer->forward("C");printf("%s\n", fa.get().c_str());printf("%s\n", fb.get().c_str());printf("%s\n", fc.get().c_str());    printf("Program done.\n");return 0;
}

上述示例代码相对复杂,结合了 RAII 和接口模式来实现模拟模型推理,具体是一个消费者-生产者模式的异步批处理机制,我们来简单解读下 infer.cpp 中具体干了些啥(form chatGPT

1. 数据结构和类定义

  • Job 结构体:这是一个任务结构,包含了一个 promise 对象(用于在工作线程中设置结果)和输入数据,promise 又通过 shared_ptr 封装了一层,可以让结构体传递效率更高
  • InferImpl 类,这是 InferInterface 的实现类,包含了异步处理的核心逻辑

2. InferImpl 类的方法和成员

  • 析构函数:在对象销毁时,将 worker_running_ 标志设置为 false,并通过条件变量唤醒工作线程。然后等待工作线程结束
  • load_model 方法:模型加载函数,它实际上启动了工作线程,并传递了一个 promise 对象来设置是否成功加载了模型
  • forward 方法:这是暴露给使用者的接口,用于提交一个新的推理任务。这个方法将任务添加到队列中,并通过条件变量唤醒工作线程
  • worker 方法:这是工作线程的核心函数,它从队列中取出任务并批量处理它们,然后使用 promise 设置结果
  • 私有成员
    • worker_running_:一个原子布尔标志,表示工作线程是否正在运行
    • worker_thread_:工作线程对象
    • qjobs_:包含待处理任务的队列
    • job_lock_:保护任务队列的互斥锁
    • cv_:条件变量,用于在有新任务到来或工作线程需要停止时唤醒工作线程

3. 工厂函数

  • create_infer 函数:RAII 的体现,这个函数创建了一个 InferImpl 的实例,并尝试加载模型。如果加载失败,它将返回一个空的智能指针。

这个示例清晰地展示了如何使用 RAII 和接口模式来实现一个异步批处理机制,同时也展示了如何使用 C++11 的并发特性(如 threadpromisecondition_variable 等)来实现这种机制。

2. 问答环节

博主对多线程相关的知识不怎么了解,因此疯狂询问 chatGPT,故此做个记录方便下次查看,以下内容来自于博主和 chatGPT 之间的对话

问题1:work_running_ 为什么是 atomic<boll> 类型,为什么不直接使用 bool 类型?什么是 atomic<bool> 类型?🤔

回答atomic<bool> 是 C++11 引入的原子类型,它是为了支持多线程环境中无锁的线程安全操作。使用原子类型可以确保对该类型的读写操作是原子的,即它们不会被其他线程中的操作中断。

当多个线程可能同时访问和修改一个变量时,直接使用基本数据类型(如 bool)可能会导致不确定的行为或数据竞态(race conditions)。数据竞态通常是多线程编程中的一个问题,当两个或更多的线程同时访问同一资源,并至少有一个线程修改该资源时,结果是不确定的。

使用 atomic<bool> 的好处:

1. 原子操作atomic<bool> 保证了对其的读写操作是原子的,这意味着每个读或写操作都是不可中断的。

2. 内存顺序:原子操作还提供了内存顺序语义,这确保了在多线程环境中的操作顺序。

3. 无锁操作:与使用互斥锁相比,原子操作通常更快,因为它们通常是无锁的。

在示例中,atomic<bool> 可能会在主线程中被设置,并在工作线程中被检查。为了确保这种跨线程的通信是线程安全的,使用 atomic<bool> 是合适的。如果只使用普通的 bool,可能会导致数据竞态,从而引起不可预测的行为。

总之,atomic<bool> 提供了一种线程安全的方式来读取和修改一个布尔值,特别是在无需使用互斥锁的情况下。

问题2:我在 CUDA 编程中也听过原子操作,就是 GPU 中多个线程对同一个变量进行修改时要使用原子操作,这跟你提到的原子类型是不是类似的呢?🤔

回答:是的,CUDA 中的原子操作和 C++ 中的原子类型在概念上是非常相似的。两者都是为了确保在并发环境中对数据的访问和修改是线程安全的。

1. CUDA 中的原子操作

  • CUDA 提供了一系列原子函数,如 atomicAdd, atomicSub 等,这些函数可以确保在多个线程可能同时访问同一内存位置时,这些操作是原子的。
  • 这在 GPU 编程中尤为重要,因为 GPU 具有大量的并行执行单元,这使得多个线程可能会同时访问同一个内存位置。
  • 使用原子操作可以避免数据竞态,并确保正确的结果。

2. C++ 中的原子类型

  • C++ 的 std::atomic 类模板提供了一系列原子操作,这些操作确保在多线程环境中对数据的读取和写入是线程安全的,而无需使用互斥锁。
  • 这对于 CPU 的多
    • 线程编程非常有用,因为多个线程可能会并发地运行,并尝试访问和修改同一块内存。

两者的共同点是它们都提供了一种线程安全的方式来处理并发的数据访问和修改。不同之处在于它们分别是为 GPU 和 CPU 设计的,并具有各自的特定语境和使用场景。

总的来说,无论是 CUDA 的原子操作还是 C++ 的原子类型,其核心目的都是为了提供一种在并发环境中安全访问和修改数据的机制。

总结

本次课程学习了 RAII 接口模式下的生产者消费者多 batch 实现。把前面学的知识全都串起来了,首先通过 create_infer 创建 infer 资源并对模型进行加载,这是 RAII 体现,其次该函数返回的是接口类的智能指针,因此用户只能看到 forward 这一个接口,这是接口模式的应用

在 InferImpl 具体实现类中,我们通过 forward 函数不断向队列中添加数据,而 worker 函数则会判断队列中的数据是否为空,若不为空则进行推理,若为空则继续等待,是否等待是通过条件变量 condition_variable 的 wait 和 notify_one 来实现的,另外 worker 线程将推理结果返回到 forward 中是通过 promise 和 future 来实现,值得注意的是我们在 forward 中返回的并不是 future.get() 而是直接返回的一个 future 对象,具体什么时候 get 拿结果用使用者决定

这个示例把生产者和消费者模式、RAII接口模式以及异步机制等都结合起来,有点像 tensorRT_Pro 中推理实现部分的雏形😂

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/38079.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

原生JS手写扫雷小游戏

场景 实现一个完整的扫雷游戏需要一些复杂的逻辑和界面交互。我将为你提供一个简化版的扫雷游戏示例&#xff0c;帮助你入门。请注意&#xff0c;这只是一个基本示例&#xff0c;你可以根据自己的需求进行扩展和改进。 思路 创建游戏板&#xff08;Grid&#xff09;&#xff1…

软考:中级软件设计师:文件管理,索引文件结构,树型文件结构,位示图,数据传输方式,微内核

软考&#xff1a;中级软件设计师: 提示&#xff1a;系列被面试官问的问题&#xff0c;我自己当时不会&#xff0c;所以下来自己复盘一下&#xff0c;认真学习和总结&#xff0c;以应对未来更多的可能性 关于互联网大厂的笔试面试&#xff0c;都是需要细心准备的 &#xff08;1…

【嵌入式学习笔记】嵌入式入门7——IIC总线协议

1.IIC简介 IIC即Inter Integrated Circuit&#xff0c;集成电路总线&#xff0c;是一种同步&#xff0c;串行&#xff0c;半双工通信总线。 IIC总线协议——总线就是传输数据通道&#xff0c;协议就是传输数据的规则&#xff0c;有以下特点&#xff1a; 由时钟线SCL和数据线S…

【Linux】可重入函数 volatile关键字 以及SIGCHLD信号

可重入函数 volatile关键字 以及SIGCHLD信号 一、可重入函数1、引入2、可重入函数的判断 二、volatile关键字1、引入2、关于编译器的优化的简单讨论 三、SIGCHLD信号 一、可重入函数 1、引入 我们来先看一个例子来帮助我们理解什么是可重入函数&#xff1a; 假设我们现在要对…

EthGlobal 巴黎站 Chainlink 获奖项目介绍

在 Web3 中&#xff0c;每一周都至关重要。项目的发布、版本的发布以及协议的更新以惊人的速度推出。开发者必须保持学习&#xff0c;随时了解最新的工具&#xff0c;并将所有他们所学的东西&#xff08;无论是旧的还是新的&#xff09;联系起来&#xff0c;以构建推动 Web3 技…

PLUS操作流程、应用与实践,多源不同分辨率数据的处理、ArcGIS的应用、PLUS模型的应用、InVEST模型的应用

PLUS模型是由中国地质大学&#xff08;武汉&#xff09;地理与信息工程学院高性能空间计算智能实验室开发&#xff0c;是一个基于栅格数据的可用于斑块尺度土地利用/土地覆盖(LULC)变化模拟的元胞自动机(CA)模型。PLUS模型集成了基于土地扩张分析的规则挖掘方法和基于多类型随机…

Word转PDF在线转换如何操作?分享转换技巧

现如今&#xff0c;pdf转换器已成为大家日常办公学习必不可少的工具&#xff0c;市场上的pdf转换器主要有两种类型&#xff0c;一种是需要下载安装的&#xff0c;另一种是网页版&#xff0c;打开就可以使用的&#xff0c;今天小编给大家推荐一个非常好用的网页版pdf转换器&…

实验篇——亚细胞定位

实验篇——亚细胞定位 文章目录 前言一、亚细胞定位的在线网站1. UniProt2. WoLFPSORT3. BUSCA4. TargetP-2.0 二、代码实现1. 基于UniProt&#xff08;不会&#xff09;2. 基于WoLFPSORT后续&#xff08;已完善&#xff0c;有关代码放置于[python爬虫学习&#xff08;一&#…

软件测试简历撰写与优化,让你面试邀约率暴增99%!

如何撰写一份优秀的简历呢&#xff1f;&#xff1f;这是一个求职者都会遇到的问题&#xff0c;今天就来详细带大家写一份软件测试工程师职位的简历&#xff01;希望能给各位软件测试求职者一个带来帮助&#xff01; 个人简历是求职者给招聘单位发的一份简要介绍。包含自己的基本…

linux系统服务学习(一)Linux高级命令扩展

文章目录 Linux高级命令&#xff08;扩展&#xff09;一、find命令1、find命令作用2、基本语法3、*星号通配符4、根据文件修改时间搜索文件☆ 聊一下Windows中的文件时间概念&#xff1f;☆ 使用stat命令获取文件的最后修改时间☆ 创建文件时设置修改时间以及修改文件的修改时间…

【Vue】Vue2创建移动端项目实战教程,创建移动端项目保姆级教程,设置axios,utils工具包,vue.fonfig.js配置项 (下)

系列文章目录 这里是创建移动端项目 【Vue】Vue2.x创建项目全程讲解&#xff0c;保姆级教程&#xff0c;手把手教&#xff0c;Vue2怎么创建项目&#xff08;上&#xff09; 【Vue】Vue2创建移动端项目实战教程&#xff0c;创建移动端项目保姆级教程&#xff0c;接上一篇创建Vue…

玩赚音视频开发高阶技术——FFmpeg

随着移动互联网的普及&#xff0c;人们对音视频内容的需求也不断增加。无论是社交媒体平台、电商平台还是在线教育&#xff0c;都离不开音视频的应用。这就为音视频开发人员提供了广阔的就业机会。根据这些年来网站上的音视频开发招聘需求来看&#xff0c;音视频开发人员的需求…

如何优雅的使用Mock Server

事出有因 昨天跟同事讨论我们在用的rap2(一个集接口编写和mock server的开源项目)和刚上线了一个easy-mock的server&#xff0c;到底哪个好用。 我们主要讨论的点有个两个&#xff1a; 接口的一致性、 编码的无侵入性。 背景 自从前后端分离后&#xff0c;完成前后端的分工…

【计算机视觉|生成对抗】条件生成对抗网络(CGAN)

本系列博文为深度学习/计算机视觉论文笔记&#xff0c;转载请注明出处 标题&#xff1a;Conditional Generative Adversarial Nets 链接&#xff1a;[1411.1784] Conditional Generative Adversarial Nets (arxiv.org) 摘要 生成对抗网络&#xff08;Generative Adversarial…

Windows 11 家庭中文版找不到组策略文件gpedit.msc

最近因为调整日期问题需要用到组策略文件gpedit.msc,但是发现找不到文件 在按键盘 winR 打开运行界面输入 gpedit.msc 回车 Windows找不到文件’gpedit.msc’。请确定文件名是否正确后&#xff0c;再试-次。 检查电脑Windows系统版本 是 Windows 11 家庭中文版 果断早网上搜…

Jmeter 参数化的几种方法

目录 配置元件-用户自定义变量 前置处理器-用户参数 配置元件-CSV Data Set Config Tools-函数助手 配置元件-用户自定义变量 可在测试计划、线程组、HTTP请求下创建用户定义的变量 全局变量&#xff0c;可以跨线程组调用 jmeter执行的时候&#xff0c;只获取一次&#xff0…

kafka 02——三个重要的kafka客户端

kafka 02——三个重要的kafka客户端 1. 前言1.1 关于 Kafka 的安装1.2 常用客户端简介1.3 依赖 2. AdminClient2.1 Admin Configs2.2 AdminClient API2.2.1 设置 AdminClient 对象2.2.2 创建 topic 获取 topic 列表2.2.3 删除topic2.2.4 查看 topic 的描述信息2.2.5 查看 topi…

【腾讯云 TDSQL-C Serverless 产品体验】基于TDSQL-C 存储爬取的QQ音乐歌单数据

【腾讯云 TDSQL-C Serverless 产品体验】基于TDSQL-C 存储爬取的QQ音乐歌单数据 文章目录 【腾讯云 TDSQL-C Serverless 产品体验】基于TDSQL-C 存储爬取的QQ音乐歌单数据前言出现的背景一、TDSQL-C数据库是什么&#xff1f;二、TDSQL-C 的特点三、TDSQL-C的应用场景四、基于TD…

测试相关Liunx基础知识

Linux的历史和安装 基本常识 Liunx目录结果 常见

CTF之逆向之阿里巴巴

题目地址&#xff1a;http://www.shiyanbar.com/ctf/13 题目预览&#xff1a; 解题过程&#xff1a; 1、下载附件发现是exe文件 2、使用PEid和Detect It Easy查壳 和 开发语言&#xff0c;发现没有加壳&#xff0c;都是用C#开发的 3、C#和Java Python属于解释型语言&#xff…