一文彻底搞懂为什么OpenCV用GPU/cuda跑得比用CPU慢?

这篇具有很好参考价值的文章主要介绍了一文彻底搞懂为什么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。

//执行这些简单算子,CPU比GPU更快

cvtColor,GaussianBlur,Canny

//执行这些耗时算子,GPU比CPU更快

HoughCircles,HoughLines,matchTemplate

5、如果问题规模较小,逻辑控制较为复杂,并行性很小优先使用CPU处理该问题,如果包含较大规模的数据处理,则考虑使用GPU进行处理。

CPU上线程是重量级实体,可以开启1~32个线程,且上下文切换较为缓慢,GPU上线程是高度轻量级的,可以开几百甚至上千个线程。

CUDA通过两种API来对设备GPU设备进行控制,包括驱动API和运行API,其中驱动API较难编程,但是设备控制能力和利用率高。两者只能选择其中一种,不能混合使用。

一个CUDA程序包含了两个部分代码,在CPU上运行的主机代码和在GPU上运行的设备代码。

6、总结一句,GPU的并行处理的确很快,但数据传入GPU和传出的开销实在太大,往往影响了代码的整体效率,运算量非常小的计算不要用GPU。

二、举例opencv

#include <iostream>
#include <opencv2/core.hpp>
#include <opencv2/core/cuda.hpp>
#include <opencv2/core/ocl.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/cudaimgproc.hpp>
#include <opencv2/cudaarithm.hpp>
#include <opencv2/cudafilters.hpp>
#include <opencv2/cudawarping.hpp>

#define IMAGE_TEST_PATHNAME "D:\\test_src.jpg"
#define IMAGE_SOURCE        "D:\\test_src.jpg"
#define IMAGE_TEMPLATE      "D:\\test_templ.jpg"

void checkCuda() //旧版本的是cv::gpu,#include <opencv2/gpu/gpu.hpp>,已弃用
{
    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);
}

void calcEdgesCuda()
{
    cv::ocl::setUseOpenCL(false);

    double start = cv::getTickCount();
    cv::cuda::GpuMat gpuGray, gpuBlur, gpuEdges;
    cv::Mat cpuEdges;

    cv::Mat cpuFrame = cv::imread(IMAGE_PATHNAME);

    cv::cuda::registerPageLocked(cpuFrame); //锁页内存

    cv::cuda::GpuMat gpuFrame;
    gpuFrame.upload(cpuFrame);

    cv::cuda::cvtColor(gpuFrame, gpuGray, cv::COLOR_BGR2GRAY);

    cv::Ptr<cv::cuda::Filter> gaussFilter = cv::cuda::createGaussianFilter(CV_8UC1, CV_8UC1, cv::Size(3, 3), 15, 15);
    gaussFilter->apply(gpuGray, gpuBlur);

    cv::Ptr<cv::cuda::CannyEdgeDetector> cannyEdge = cv::cuda::createCannyEdgeDetector(50, 100, 3);
    cannyEdge->detect(gpuBlur, gpuEdges);

    cv::cuda::GpuMat gpuLines; //This should be GpuMat...
#if 0                          //find line
    std::vector<cv::Vec2f> vtLines;
    cv::Ptr<cv::cuda::HoughLinesDetector> hough = cv::cuda::createHoughLinesDetector(1, CV_PI / 180, 120);
    hough->detect(gpuEdges, gpuLines);
    hough->downloadResults(gpuLines, vtLines);
#else
    cv::Ptr<cv::cuda::HoughCirclesDetector> hough1 = cv::cuda::createHoughCirclesDetector(1.5, 15, 300, 1, 1, 100);
    hough1->detect(gpuEdges, gpuLines);
    cv::Ptr<cv::cuda::HoughCirclesDetector> hough2 = cv::cuda::createHoughCirclesDetector(1, 15, 100, 30, 1, 100);
    hough2->detect(gpuEdges, gpuLines);
#endif

    gpuEdges.download(cpuEdges);
    cv::cuda::unregisterPageLocked(cpuFrame); //解除锁页

    std::cout << "Cuda cost time:(s)" << ((cv::getTickCount() - start) / cv::getTickFrequency()) << std::endl;

    cv::namedWindow("Canny Edges Cuda", cv::WINDOW_NORMAL);
    cv::imshow("Canny Edges Cuda", cpuEdges);
}

void matchTemplateCPU()
{
    cv::Mat src = cv::imread(IMAGE_SOURCE, cv::IMREAD_GRAYSCALE);
    cv::Mat templ = cv::imread(IMAGE_TEMPLATE, cv::IMREAD_GRAYSCALE);
    cv::Mat dst;
    double minVal = 0;
    double maxVal = 0;
    cv::Point minLoc;
    cv::Point maxLoc;

    double start = cv::getTickCount();
    cv::matchTemplate(src, templ, dst, cv::TM_CCOEFF_NORMED); //用6种匹配方式
    cv::normalize(dst, dst, 1, 0, cv::NORM_MINMAX);
    cv::minMaxLoc(dst, &minVal, &maxVal, &minLoc, &maxLoc); //找到最佳匹配点
    std::cout << "matchTemplateCPU cost time:(s)" << ((cv::getTickCount() - start) / cv::getTickFrequency()) << std::endl;

    cv::rectangle(src, cv::Rect(maxLoc.x, maxLoc.y, templ.cols, templ.rows), 1, 8, 0);

    cv::namedWindow("matchTemplateCPU", cv::WINDOW_NORMAL);
    cv::imshow("matchTemplateCPU", src);
}

void matchTemplateCuda()
{
    cv::Mat src = cv::imread(IMAGE_SOURCE, cv::IMREAD_GRAYSCALE);
    cv::Mat templ = cv::imread(IMAGE_TEMPLATE, cv::IMREAD_GRAYSCALE);
    cv::Mat dst;
    double minVal = 0;
    double maxVal = 0;
    cv::Point minLoc;
    cv::Point maxLoc;
    cv::cuda::GpuMat gsrc, gtempl, gdst;

    double start = cv::getTickCount();
    gsrc.upload(src);
    gtempl.upload(templ);
    cv::Ptr<cv::cuda::TemplateMatching> matcher;
    matcher = cv::cuda::createTemplateMatching(CV_8U, cv::TM_CCOEFF_NORMED);
    matcher->match(gsrc, gtempl, gdst);
    cv::cuda::minMaxLoc(gdst, &minVal, &maxVal, &minLoc, &maxLoc);
    std::cout << "matchTemplateCuda cost time:(s)" << ((cv::getTickCount() - start) / cv::getTickFrequency()) << std::endl;

    cv::rectangle(src, cv::Rect(maxLoc.x, maxLoc.y, templ.cols, templ.rows), 1, 8, 0);

    cv::namedWindow("matchTemplateCuda", cv::WINDOW_NORMAL);
    cv::imshow("matchTemplateCuda", src);
}

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

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

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

---

姊妹篇

OpenCV算法加速(4)官方源码v4.5.5的默认并行和优化加速的编译选项是什么?请重点关注函数cv::getBuildInformation()的返回值_opencv 编译选项_利白的博客-CSDN博客

参考文献

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

opencv(C++)GPU、CPU 模板匹配_opencv 操作gpu_1037号森林里一段干木头的博客-CSDN博客

cuda实现的连通域

https://docs.nvidia.com/cuda/npp/group__image__filter__label__markers.html

CV-CUDA™ is an open-source, GPU accelerated library for cloud-scale image processing and computer vision.

https://github.com/CVCUDA/CV-CUDA

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

作者:景乃锋、柯晶、梁晓 出版社:清华大学出版社 出版时间:2022年05月文章来源地址https://www.toymoban.com/news/detail-439131.html

到了这里,关于一文彻底搞懂为什么OpenCV用GPU/cuda跑得比用CPU慢?的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 一文了解以太坊为什么合并及未来升级规划

    一、什么是以太坊升级 以太坊升级最初被称为「ETH 2.0」或「宁静(Serenity)」,是一次规划已久的以太坊网络重大升级,将使以太坊网络具有更好的可扩展性、安全性和可持续性。 二、为什么要进行升级 众所周知,以太坊正面临着网络拥堵、运行节点门槛高、能源损耗大等

    2023年04月08日
    浏览(45)
  • 一文读懂为什么需要跨链?跨链是什么?跨链实现技术?

    区块链的现状 从2014开始,“区块链2.0”成为一个关于去中心化区块链数据库的术语。区块链2.0 技术跳过了交易和价值交换中担任金钱和信息仲裁的中介机构。这使得人们的隐私得到保护,可以将掌握的信息兑换成货币,并且有能力保证知识产权的所有者得到收益。 从狭义角

    2024年01月23日
    浏览(46)
  • 从驾考科目二到自动驾驶,聊聊GPU为什么对自动驾驶很重要

    “下一个项目,坡道起步。” …… “考试不合格,请将车子开到起点,重新验证考试。你的扣分项是:起步时间超30秒:扣100分。行驶过程中车轮轧到边线:扣100分。” 想必经历过驾驶证考试的同学,对科目二的坡道起步都有说不清道不明的情感。我在坡道起步项目上连续

    2024年02月09日
    浏览(33)
  • iNFTnews|一文读懂为什么说元宇宙是未来

    我敢肯定,我们中的任何人都将知道“元宇宙”这个。在去年(2021年)的这个时候,元宇宙已经成为继NFT、GameFi等之后加密货币新的大趋势。 在马克·扎克伯格(Mark Zuckerberg)宣布Facebook从“社交媒体公司”转型为“元宇宙公司”并将Facebook正式更名为Meta之后,元宇宙

    2024年01月22日
    浏览(61)
  • 一文看懂什么是欧几里得算法!多图演示辗转相除算法究竟是什么!为什么要这样开展!多图预警!

    ps:全文图片均为手绘,如果有不标准的地方还望谅解,之后会慢慢熟悉画图工具的,感谢感谢!!! 欧几里得算法 又称为 辗转相除法 ,是指用于计算两个非负整数a,b的最大 公约数 。 两个整数的最大公约数是指能够同时整除它们的最大的正整数。 辗转相除法能够实现效

    2024年02月02日
    浏览(48)
  • 你的 Redis为什么变慢了?一文讲透Redis性能优化如何做

    对 Redis 进行基准性能测试 例如,我的机器配置比较低,当延迟为 2ms 时,我就认为 Redis 变慢了,但是如果你的硬件配置比较高,那么在你的运行环境下,可能延迟是 0.5ms 时就可以认为 Redis 变慢了。 所以,你只有了解了你的 Redis 在生产环境服务器上的基准性能,才能进一步

    2024年02月02日
    浏览(60)
  • 【Day1】零基础学java--》记事本运行java程序,通熟语言让你彻底明白为什么配置java环境变量

    前言: 大家好,我是 良辰丫 ,从今天开始我将协同大家一起从零基础学习Java,期待与君为伴,走向海的彼岸。💕💕💕 🧑个人主页:良辰针不戳 📖所属专栏:EveryDay零基础学java 🍎励志语句:生活也许会让我们遍体鳞伤,但最终这些伤口会成为我们一辈子的财富。 💦期

    2024年02月11日
    浏览(45)
  • tensorflow-gpu安装100%成功(tensorflow-gpu版和tensorflow-cpu版的区别、为什么要创建虚拟环境、如何同时使用两个gpu库、tensorflow-gpu版安装)

    1.tensorflow-gpu版和tensorflow-cpu版的区别 tensorflow-gpu版需要同时配置安装CUDA、cuDNN,而tensorflow-cpu版不需要配置,直接 pip/conda install tensorflow 即可安装tensorflow-cpu版本 2.为什么要创建虚拟环境 在安装gpu版本的库时通常会创建单独的虚拟环境,例如安装tensorflow-gpu,则需要利用 cond

    2024年02月08日
    浏览(57)
  • 【真情流露】我为什么要写一本OpenCV C++书籍

    使用OpenCV契机 大家好,我是贾志刚,OpenCV学堂公众号的号主,从2009年开始搞图像处理到今天我已经十四年了。刚开始搞图像处理做的是生物数据分析与细胞分析,用的是工具跟SDK是ImageJ这个框架,多数算法都是我自己裸写,不依赖任何库。直到2014年的一天有个朋友跟我说你

    2024年02月04日
    浏览(39)
  • 一文彻底搞懂JSON数据

    什么是JSON,为什么需要JSON,JSON的3种形式,JSON常用的方法等 TIP JSON指的是全称是:javascript对象表示法 JSON是Ajax发送和接收数据的一种格式 JSON是一种轻量级的数据交互格式, 其为字符串类型 (面试题会考到) JSON是一种语法,用来序列化对象、数组、数值、字符串、布尔值和

    2024年02月06日
    浏览(52)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包