一、原因總結(jié)
最近項(xiàng)目需要,發(fā)現(xiàn)了這個(gè)問題。網(wǎng)上找原因,匯總起來,有以下幾點(diǎn)原因:
1、首先對(duì)于任何一個(gè)CUDA程序,在調(diào)用它的第一個(gè)CUDA API時(shí)后都要花費(fèi)秒級(jí)的時(shí)間去初始化運(yùn)行環(huán)境,后續(xù)還要分配顯存,傳輸數(shù)據(jù),啟動(dòng)內(nèi)核,每一樣都有延遲。這樣如果你一個(gè)任務(wù)CPU運(yùn)算都僅要幾十毫秒,相比而言必須帶上這些延遲的GPU程序就會(huì)顯得非常慢。
2、其次,一個(gè)運(yùn)算量很小的程序,你的CUDA內(nèi)核不可能啟動(dòng)太多的線程,沒有足夠的線程來屏蔽算法執(zhí)行時(shí)從顯存加載數(shù)據(jù)到GPU SM中的時(shí)延,這就沒有發(fā)揮GPU的真正功能。
3、數(shù)據(jù)從內(nèi)存?zhèn)鬟f到顯存和cudaMalloc耗時(shí)很長,NVIDIA提供的nsight中的profile可以看每一個(gè)部分的耗時(shí)?;旧螼penCV的算法都?xì)w納為三個(gè)部分:upload(gpu::Mat), processCodeBlock, download(gpu::Mat)。你看看是不是80%以上的時(shí)間都花在第一個(gè)和最后一個(gè)上,問題就迎刃而解了。因?yàn)間pu在計(jì)算上雖然比cpu快,但實(shí)際上在使用gpu的時(shí)候有一步非常耗時(shí),那就是將內(nèi)存與顯存中的數(shù)據(jù)進(jìn)行互相拷貝,同時(shí)這也是使用gpu運(yùn)算時(shí)逃不掉的一步。
4、GPU擅長的是大規(guī)模并行計(jì)算,比起cpu只是以巨額核心數(shù)取得優(yōu)勢(shì)的,單核速度其實(shí)被cpu碾壓。如果數(shù)據(jù)規(guī)模小的話GPU并不能用上太多核,所以比cpu慢。減少數(shù)據(jù)在CPU和GPU之間的傳遞次數(shù);運(yùn)算量非常小的部分不要用GPU,數(shù)據(jù)量非常大、循環(huán)次數(shù)非常多的時(shí)候才使用GPU。
//執(zhí)行這些簡單算子,CPU比GPU更快
cvtColor,GaussianBlur,Canny
//執(zhí)行這些耗時(shí)算子,GPU比CPU更快
HoughCircles,HoughLines,matchTemplate
5、如果問題規(guī)模較小,邏輯控制較為復(fù)雜,并行性很小優(yōu)先使用CPU處理該問題,如果包含較大規(guī)模的數(shù)據(jù)處理,則考慮使用GPU進(jìn)行處理。
CPU上線程是重量級(jí)實(shí)體,可以開啟1~32個(gè)線程,且上下文切換較為緩慢,GPU上線程是高度輕量級(jí)的,可以開幾百甚至上千個(gè)線程。
CUDA通過兩種API來對(duì)設(shè)備GPU設(shè)備進(jìn)行控制,包括驅(qū)動(dòng)API和運(yùn)行API,其中驅(qū)動(dòng)API較難編程,但是設(shè)備控制能力和利用率高。兩者只能選擇其中一種,不能混合使用。
一個(gè)CUDA程序包含了兩個(gè)部分代碼,在CPU上運(yùn)行的主機(jī)代碼和在GPU上運(yùn)行的設(shè)備代碼。
6、總結(jié)一句,GPU的并行處理的確很快,但數(shù)據(jù)傳入GPU和傳出的開銷實(shí)在太大,往往影響了代碼的整體效率,運(yùn)算量非常小的計(jì)算不要用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); //指定顯卡
//有一個(gè)問題是,一般使用GPU加速的話,第一次調(diào)用GPU,會(huì)很慢很慢,一條簡單的語句都用了10多秒左右。
//治標(biāo)不治本的解決方法是在程序的開頭加上一句cv::gpu::GpuMata(10, 10, CV_8U);
//這樣會(huì)令耗時(shí)的操作放在一開頭,不那么影響后面的操作時(shí)間
//為什么第一次函數(shù)調(diào)用很慢
//那是因?yàn)槌跏蓟_銷;在第一個(gè)GPU函數(shù)調(diào)用Cuda Runtime API被隱式初始化;
cv::cuda::GpuMat(10, 10, CV_8U);
//測(cè)試用例
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); //鎖頁內(nèi)存
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); //找到最佳匹配點(diǎn)
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);
}
鎖頁能夠加速數(shù)據(jù)在CPU和GPU之間的傳遞
cv::cuda::registerPageLocked(img);//鎖頁內(nèi)存
gimg.upload(img);//上傳數(shù)據(jù)至GPU
gimg.download(img);//下載數(shù)據(jù)至CPU
cv::cuda::unregisterPageLocked(img);//解除鎖頁
---
姊妹篇
OpenCV算法加速(4)官方源碼v4.5.5的默認(rèn)并行和優(yōu)化加速的編譯選項(xiàng)是什么?請(qǐng)重點(diǎn)關(guān)注函數(shù)cv::getBuildInformation()的返回值_opencv 編譯選項(xiàng)_利白的博客-CSDN博客
參考文獻(xiàn)
為什么opencv用GPU實(shí)現(xiàn)比用CPU實(shí)現(xiàn)的慢?_opencv在顯卡上和cpu上跑程序哪個(gè)快_THMAIL的博客-CSDN博客
opencv(C++)GPU、CPU 模板匹配_opencv 操作gpu_1037號(hào)森林里一段干木頭的博客-CSDN博客
cuda實(shí)現(xiàn)的連通域
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
《通用圖形處理器設(shè)計(jì)——GPGPU編程模型與架構(gòu)原理》文章來源:http://www.zghlxwxcb.cn/news/detail-439131.html
作者:景乃鋒、柯晶、梁曉 出版社:清華大學(xué)出版社 出版時(shí)間:2022年05月文章來源地址http://www.zghlxwxcb.cn/news/detail-439131.html
到了這里,關(guān)于一文徹底搞懂為什么OpenCV用GPU/cuda跑得比用CPU慢?的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!