为何C++中使用更多独立线程时TensorRT enqueueV2耗时增加?
问题分析与解决方案
环境信息
- 操作系统:Windows 10
- CUDA版本:11.5
- TensorRT版本:8.6.1.6
- OpenCV版本:4.8.0(带CUDA编译)
- 驱动版本:最新版(545.84)
问题场景
应用需处理多路相机流,每个相机由独立CPU线程管理,线程间无资源共享;每个线程加载并使用独立TensorRT目标检测模型,模型不共享。
测试情况
- 2线程测试:TensorRT
enqueueV2平均耗时约1ms,Nsight System显示单线程enqueueV2耗时238μs,性能正常。 - 20线程测试:
enqueueV2耗时增至约20ms,线程数越多耗时越长,Nsight System报告enqueueV2耗时19ms,FPS严重下降。
核心原因:Blocked States的本质
你看到的Blocked States确实是性能骤降的根因,本质是CUDA设备端资源竞争和CPU线程与CUDA流的同步逻辑不合理:
- GPU全局资源瓶颈:即使每个线程用独立模型和流,GPU的SM(流式多处理器)、显存带宽、硬件调度器等核心资源是全局共享的。20线程同时发起推理,会导致SM被占满,推理任务排队等待,体现为
enqueueV2后的同步阻塞。 - 同步方式放大延迟:你用
cudaEventSynchronize直接阻塞CPU线程等待CUDA流完成,当GPU资源紧张时,CPU线程会进入长时间阻塞状态,被Nsight标记为Blocked States。 - Windows下CUDA上下文开销:每个线程独立加载模型,会创建多个CUDA上下文,Windows环境下上下文切换成本较高,进一步加剧阻塞。
解决方法
1. 优化CUDA流与同步逻辑
不要在enqueueV2后立即同步CPU和GPU,改用异步回调让CPU线程不被阻塞:
auto t1 = std::chrono::high_resolution_clock::now(); status = mContext->enqueueV2(&mBindingDataHolder[0], *inferenceCudaStream, nullptr); // 注册异步回调,推理完成后再计算耗时 CUDA_CHECK(cudaStreamAddCallback(*inferenceCudaStream, [](cudaStream_t stream, cudaError_t status, void* data) { auto t2 = std::chrono::high_resolution_clock::now(); auto* t1_ptr = static_cast<std::chrono::high_resolution_clock::time_point*>(data); auto duration = std::chrono::duration_cast<std::chrono::microseconds>(t2 - *t1_ptr).count(); spdlog::info("enqueueV2 actual inference time: {} μs", duration); delete t1_ptr; }, new std::chrono::high_resolution_clock::time_point(t1), 0));
这样CPU线程可立即返回处理下一个相机帧,Blocked States会大幅减少。
2. 限制并发推理的GPU资源占用
- 控制并发线程数:根据GPU的SM数量设置上限(比如RTX 3090有82个SM,可设置16-24个并发线程),避免GPU资源饱和。
- 优化模型显存占用:创建
ICudaEngine时,通过IBuilderConfig::setMaxWorkspaceSize限制每个模型的显存占用,减少显存带宽竞争。 - 启用TensorRT量化:将模型转为FP16或INT8格式,降低单推理任务的计算量和显存占用。
3. 优化CUDA上下文管理
- 提前在主线程初始化所有模型的CUDA上下文,避免每个线程重复创建。
- 用
cudaSetDevice固定GPU设备,减少设备切换开销(多GPU场景下可将线程分配到不同GPU)。
4. 用Nsight精准定位阻塞点
查看Nsight System的CUDA Kernel Timeline:
- 如果是
cudaEventSynchronize导致CPU阻塞,优先优化同步逻辑。 - 如果是Kernel Execution阶段阻塞,说明GPU资源饱和,需减少并发数或优化模型。
内容的提问来源于stack exchange,提问作者sms holding
相关产品推荐
相关产品推荐

