网站搭建公司排名,照明灯具类企业网站,做家常菜哪个网站最好,asp网站变成php目录 前言1. RAII接口模式封装生产者消费者2. 问答环节总结 前言 杜老师推出的 tensorRT从零起步高性能部署 课程#xff0c;之前有看过一遍#xff0c;但是没有做笔记#xff0c;很多东西也忘了。这次重新撸一遍#xff0c;顺便记记笔记。 本次课程学习 tensorRT 高级-RAI… 目录 前言1. RAII接口模式封装生产者消费者2. 问答环节总结 前言 杜老师推出的 tensorRT从零起步高性能部署 课程之前有看过一遍但是没有做笔记很多东西也忘了。这次重新撸一遍顺便记记笔记。 本次课程学习 tensorRT 高级-RAII 接口模式下的生产者消费者多 batch 实现 课程大纲可看下面的思维导图 1. RAII接口模式封装生产者消费者 这节课我们利用上节课学到的 RAII 接口模式对我们的消费者生产者进行封装 我们来看代码
infer.hpp
#ifndef INFER_HPP
#define INFER_HPP#include memory
#include string
#include futureclass InferInterface{
public:virtual std::shared_futurestd::string forward(std::string pic) 0;
};std::shared_ptrInferInterface create_infer(const std::string file);#endif // INFER_HPPinfer.cpp
#include infer.hpp
#include thread
#include queue
#include mutex
#include futureusing namespace std;struct Job{shared_ptrpromisestring 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){// 尽量保证资源哪里分配哪里释放哪里使用这样使得程序足够简单而不是太乱// 线程内传回返回值的问题promisebool pro;worker_running_ true;worker_thread_ thread(InferImpl::worker, this, file, std::ref(pro));return pro.get_future().get();}virtual shared_futurestring forward(string pic) override{// printf(使用 %s 进行推理\n, context_.c_str());// 往队列抛任务Job job;job.pro.reset(new promisestring());job.input pic;lock_guardmutex l(job_lock_);qjobs_.push(job);// 被动通知一旦有新的任务需要推理通知我即可// 发生通知的家伙cv_.notify_one();return job.pro-get_future();}// 实际执行模型推理的部分void worker(string file, promisebool pro){// worker内实现模型的加载使用释放string context file;if(context.empty()){pro.set_value(false);return;}else{pro.set_value(true);}int max_batch_size 5;vectorJob jobs;int batch_id 0;while(worker_running_){// 等待接受的家伙// 在队列取任务并执行的过程unique_lockmutex 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:atomicbool worker_running_{false};thread worker_thread_;queueJob qjobs_;mutex job_lock_;condition_variable cv_;
};shared_ptrInferInterface create_infer(const string file){shared_ptrInferImpl instance(new Infer());if(!instance-load_model(file))instance.reset();return instance;
}main.cpp
#include infer.hppint 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 和接口模式来实现一个异步批处理机制同时也展示了如何使用 C11 的并发特性如 thread、promise、condition_variable 等来实现这种机制。
2. 问答环节 博主对多线程相关的知识不怎么了解因此疯狂询问 chatGPT故此做个记录方便下次查看以下内容来自于博主和 chatGPT 之间的对话 问题1work_running_ 为什么是 atomicboll 类型为什么不直接使用 bool 类型什么是 atomicbool 类型 回答atomicbool 是 C11 引入的原子类型它是为了支持多线程环境中无锁的线程安全操作。使用原子类型可以确保对该类型的读写操作是原子的即它们不会被其他线程中的操作中断。 当多个线程可能同时访问和修改一个变量时直接使用基本数据类型如 bool可能会导致不确定的行为或数据竞态race conditions。数据竞态通常是多线程编程中的一个问题当两个或更多的线程同时访问同一资源并至少有一个线程修改该资源时结果是不确定的。 使用 atomicbool 的好处 1. 原子操作atomicbool 保证了对其的读写操作是原子的这意味着每个读或写操作都是不可中断的。 2. 内存顺序原子操作还提供了内存顺序语义这确保了在多线程环境中的操作顺序。 3. 无锁操作与使用互斥锁相比原子操作通常更快因为它们通常是无锁的。 在示例中atomicbool 可能会在主线程中被设置并在工作线程中被检查。为了确保这种跨线程的通信是线程安全的使用 atomicbool 是合适的。如果只使用普通的 bool可能会导致数据竞态从而引起不可预测的行为。 总之atomicbool 提供了一种线程安全的方式来读取和修改一个布尔值特别是在无需使用互斥锁的情况下。 问题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 中推理实现部分的雏形