为什么OpenCV用GPU/cuda跑得比用CPU慢?

一、原因总结

最近项目需要,发现了这个问题。网上找原因,汇总起来,有以下几点原因:

1、首先对于任何一个CUDA程序,在调用它的第一个CUDA API时后都要花费秒级的时间去初始化运行环境,后续还要分配显存,传输数据,启动内核,每一样都有延迟。这样如果你一个任务CPU运算都仅要几十毫秒,相比而言必须带上这些延迟的GPU程序就会显得非常慢。

2、其次,一个运算量很小的程序,你的CUDA内核不可能启动太多的线程,没有足够的线程来屏蔽算法执行时从显存加载数据到GPU SM中的时延,这就没有发挥GPU的真正功能。

3、数据从内存传递到显存和cudaMalloc耗时很长,NVIDIA提供的nsight中的profile可以看每一个部分的耗时。基本上OpenCV的算法都归纳为三个部分:upload(gpu::Mat), processCodeBlock, download(gpu::Mat)。你看看是不是80%以上的时间都花在第一个和最后一个上,问题就迎刃而解了。因为gpu在计算上虽然比cpu快,但实际上在使用gpu的时候有一步非常耗时,那就是将内存与显存中的数据进行互相拷贝,同时这也是使用gpu运算时逃不掉的一步。

4、GPU擅长的是大规模并行计算,比起cpu只是以巨额核心数取得优势的,单核速度其实被cpu碾压。如果数据规模小的话GPU并不能用上太多核,所以比cpu慢。减少数据在CPU和GPU之间的传递次数;运算量非常小的部分不要用GPU,数据量非常大、循环次数非常多的时候才使用GPU。

5、总结一句,GPU的并行处理的确很快,但数据传入GPU和传出的开销实在太大,往往影响了代码的整体效率。

二、举例opencv

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define IMAGE_PATHNAME "D:\\test2.jpg"

void checkCuda() //旧版本的是cv::gpu,#include ,已弃用
{
    int64 begintime, endtime;
    int num_devices = cv::cuda::getCudaEnabledDeviceCount();
    if (num_devices <= 0)
    {
        std::cerr << "There is no cuda device" << std::endl;
        return;
    }

    int enable_device_id = -1;
    for (int i = 0; i < num_devices; i++)
    {
        cv::cuda::DeviceInfo dev_info(i);
        if (dev_info.isCompatible())
        {
            enable_device_id = i;
        }
    }

    if (enable_device_id < 0)
    {
        std::cerr << "GPU module isn't built for GPU" << std::endl;
        return;
    }

    cv::cuda::setDevice(enable_device_id); //指定显卡

    //有一个问题是,一般使用GPU加速的话,第一次调用GPU,会很慢很慢,一条简单的语句都用了10多秒左右。
    //治标不治本的解决方法是在程序的开头加上一句cv::gpu::GpuMata(10, 10, CV_8U);
    //这样会令耗时的操作放在一开头,不那么影响后面的操作时间
    //为什么第一次函数调用很慢
    //那是因为初始化开销;在第一个GPU函数调用Cuda Runtime API被隐式初始化;
    cv::cuda::GpuMat(10, 10, CV_8U);

    //测试用例
    cv::Mat src_image = cv::imread(IMAGE_PATHNAME);
    cv::Mat dst_image;
    cv::cuda::GpuMat d_src_img(src_image); //upload src image to gpu
    //或者d_src_img.upload(src_image);
    cv::cuda::GpuMat d_dst_img;

    begintime = cv::getTickCount();
    cv::cuda::cvtColor(d_src_img, d_dst_img, cv::COLOR_BGR2GRAY); //canny
    d_dst_img.download(dst_image);                                //download dst image to cpu
    endtime = cv::getTickCount();
    std::cerr << 1000 * (endtime - begintime) / cv::getTickFrequency() << std::endl;

    cv::namedWindow("checkCuda", cv::WINDOW_NORMAL);
    cv::imshow("checkCuda", dst_image);
    cv::waitKey(0);
}

锁页能够加速数据在CPU和GPU之间的传递

cv::cuda::registerPageLocked(img);//锁页内存
gimg.upload(img);//上传数据至GPU

gimg.download(img);//下载数据至CPU
cv::cuda::unregisterPageLocked(img);//解除锁页

---

参考文献

为什么opencv用GPU实现比用CPU实现的慢?_opencv在显卡上和cpu上跑程序哪个快_THMAIL的博客-CSDN博客

《通用图形处理器设计——GPGPU编程模型与架构原理》

作者:景乃锋、柯晶、梁晓 出版社:清华大学出版社 出版时间:2022年05月

你可能感兴趣的:(OpenCV,opencv,gpu)