OpenCV中链式CUDA操作的优化方案及自定义.cu代码调用方法咨询
针对你提出的两个核心问题——优化CUDA操作块的执行效率,以及如何调用自定义.cu代码,我会结合你的现有代码给出具体的优化方案和实现步骤:
一、最优方式优化CUDA操作块
你的代码已经用到了CUDA流(cv::cuda::Stream),这是并行化的基础,但还有几个关键优化点可以进一步提升性能:
1. 最大化计算与数据传输的重叠
当前你是先完成所有数据上传再执行计算,这会导致CPU等待GPU完成上传、GPU等待CPU上传完成的空闲时间。可以利用多流异步操作将上传、计算、下载三个阶段重叠:
- 拆分数据(如果适用)或使用多个流分别处理不同的输入数据,让一个流在上传数据时,另一个流同时执行计算。
- 注意:异步操作需要确保数据依赖关系,比如不能在数据未上传完成时就执行依赖该数据的计算。
示例优化后的上传/计算/下载流程:
cv::cuda::Stream stream1, stream2; cv::cuda::GpuMat mat1GPU, mat2GPU, resultGPU; // 异步上传,两个流并行处理 mat1GPU.upload(_mat1, stream1); mat2GPU.upload(_mat2, stream2); // 等待上传完成(如果计算依赖两个数据) stream1.waitForCompletion(); stream2.waitForCompletion(); // 异步执行计算 cv::cuda::gemm(mat1GPU, mat2GPU, 1.0, _emptyGpuMat, 0.0, resultGPU, 0, stream1); cv::cuda::threshold(mat2GPU, mat2GPU, .01, std::numeric_limits<double>::max(), cv::THRESH_TOZERO, stream2); // 异步下载结果,与后续操作(如内存释放)重叠 cv::Mat matResult; resultGPU.download(matResult, stream1); // 等待所有操作完成 stream1.waitForCompletion(); stream2.waitForCompletion();
2. 优化流内操作的并行性
观察你的代码:gemm读取_mat2GPU,随后threshold写入_mat2GPU,这两个操作存在读写数据依赖,无法在同一个流内并行。如果后续有其他无依赖的操作,应该将它们分配到不同的流中,让GPU同时执行多个任务。
3. 内存管理优化
- 避免频繁创建和销毁
GpuMat:可以提前分配好固定大小的GpuMat,重复使用,减少cudaMalloc/cudaFree的开销。 - 利用OpenCV的CUDA内存池:通过
cv::cuda::setDeviceMemoryPoolConfig配置内存池,让内存分配更高效:cv::cuda::setDeviceMemoryPoolConfig(cv::cuda::MemoryPoolType::GLOBAL, 1024*1024*512); // 配置512MB全局内存池 - 无需手动调用
release():GpuMat的析构函数会自动释放设备内存,除非你需要提前释放内存以节省空间,否则可以省略手动release()的步骤。
4. 操作融合(如果适用)
如果多个CUDA操作是针对同一组数据的连续处理,可以将它们融合成一个自定义CUDA核函数,减少数据在全局内存中的读写次数。比如,如果你的后续操作是对gemm的结果进行处理,就可以把gemm和后续操作合并到一个核函数中,避免中间结果的内存读写开销。
二、调用自定义.cu代码的方法
OpenCV的CUDA模块可以和自定义CUDA核函数无缝结合,核心是利用GpuMat的设备指针和CUDA流,具体步骤如下:
1. 编写自定义CUDA核函数
创建一个.cu文件,编写你的核函数,并封装成可以被C++调用的接口。例如,我们实现一个自定义的阈值处理核函数:
// custom_ops.cu #include <opencv2/cuda.hpp> #include <cuda_runtime.h> #include <limits> // 自定义阈值核函数 __global__ void customThresholdKernel(const double* src, double* dst, int rows, int cols, size_t step, double thresh) { // 计算线程对应的图像坐标 int x = blockIdx.x * blockDim.x + threadIdx.x; int y = blockIdx.y * blockDim.y + threadIdx.y; if (x >= cols || y >= rows) return; // 计算当前像素的设备内存地址(注意step是字节数) const double* rowSrc = src + y * (step / sizeof(double)); double* rowDst = dst + y * (step / sizeof(double)); // 阈值处理逻辑 rowDst[x] = (rowSrc[x] > thresh) ? rowSrc[x] : 0.0; } // 封装成C++接口,供主程序调用 void customThreshold(cv::cuda::GpuMat& src, cv::cuda::GpuMat& dst, double thresh, cv::cuda::Stream& stream) { // 检查输入格式 CV_Assert(src.type() == CV_64F); dst.create(src.size(), src.type()); // 配置核函数的块和网格大小(通常选择32x32的块) dim3 blockSize(32, 32); dim3 gridSize((src.cols + blockSize.x - 1) / blockSize.x, (src.rows + blockSize.y - 1) / blockSize.y); // 获取OpenCV流对应的cudaStream_t指针 cudaStream_t cudaStream = stream.cudaPtr(); // 启动核函数 customThresholdKernel<<<gridSize, blockSize, 0, cudaStream>>>( static_cast<const double*>(src.data), static_cast<double*>(dst.data), src.rows, src.cols, src.step, thresh ); // 检查核函数启动错误 CV_CUDA_CHECK(cudaGetLastError()); }
2. 编译自定义CUDA代码
使用nvcc编译.cu文件为静态库或动态库,链接到你的主程序:
# 编译为静态库 nvcc -c custom_ops.cu -o custom_ops.o -arch=sm_75 -I/path/to/opencv/include ar rcs libcustom_ops.a custom_ops.o # 主程序编译时链接该库和OpenCV的CUDA库 g++ main.cpp -o main -L/path/to/opencv/lib -lcudart -lopencv_core -lopencv_cudaarithm -L. -lcustom_ops
3. 在主程序中调用自定义函数
在主程序中包含自定义函数的头文件,然后像调用OpenCV的CUDA函数一样使用它,并且可以和OpenCV的流结合:
#include "custom_ops.h" // 声明customThreshold函数 // ... 现有代码 ... // 替换原有的cv::cuda::threshold为自定义函数 customThreshold(_mat2GPU, _mat2GPU, .01, GPUstream);
这样,自定义的CUDA核函数就可以和OpenCV的CUDA操作无缝协同工作,共享同一个CUDA流和设备内存。
内容的提问来源于stack exchange,提问作者David Fenech

