为什么Opencv GPU代码比CPU慢?

14

我正在使用notebook上的opencv242 + VS2010。
我尝试测试OpenCV中的GPU块,但结果显示GPU运行比CPU代码慢100倍。 在这段代码中,我只是将彩色图像转换为灰度图像,使用了cvtColor函数。

这是我的代码,PART1是CPU代码(测试CPU RGB2GRAY),PART2是上传图像到GPU,PART3是GPU RGB2GRAY,PART4是再次使用CPU RGB2GRAY。 有三件事让我非常惊讶:

1 在我的代码中,part1只需0.3毫秒,而与part1完全相同的part4需要40毫秒!!!
2 上传图像到GPU的part2需要6000毫秒!!!
3 Part3(GPU代码)需要11毫秒,对于这个简单的图像来说太慢了!

    #include "StdAfx.h"
    #include <iostream>
    #include "opencv2/opencv.hpp"
    #include "opencv2/gpu/gpu.hpp"
    #include "opencv2/gpu/gpumat.hpp"
    #include "opencv2/core/core.hpp"
    #include "opencv2/highgui/highgui.hpp"
    #include <cuda.h>
    #include <cuda_runtime_api.h>
    #include <ctime>
    #include <windows.h>

    using namespace std;
    using namespace cv;
    using namespace cv::gpu;

    int main()
    {
        LARGE_INTEGER freq;
        LONGLONG QPart1,QPart6;
        double dfMinus, dfFreq, dfTim;
        QueryPerformanceFrequency(&freq);
        dfFreq = (double)freq.QuadPart;

        cout<<getCudaEnabledDeviceCount()<<endl;
        Mat img_src = imread("d:\\CUDA\\train.png", 1);

        // PART1 CPU code~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        // From color image to grayscale image.
        QueryPerformanceCounter(&freq);
        QPart1 = freq.QuadPart;
        Mat img_gray;
        cvtColor(img_src,img_gray,CV_BGR2GRAY);
        QueryPerformanceCounter(&freq);
        QPart6 = freq.QuadPart;
        dfMinus = (double)(QPart6 - QPart1);
        dfTim = 1000 * dfMinus / dfFreq;
        printf("CPU RGB2GRAY running time is %.2f ms\n\n",dfTim);

        // PART2 GPU upload image~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        GpuMat gimg_src;
        QueryPerformanceCounter(&freq);
        QPart1 = freq.QuadPart;
        gimg_src.upload(img_src);
        QueryPerformanceCounter(&freq);
        QPart6 = freq.QuadPart;
        dfMinus = (double)(QPart6 - QPart1);
        dfTim = 1000 * dfMinus / dfFreq;
        printf("Read image running time is %.2f ms\n\n",dfTim);

        GpuMat dst1;
        QueryPerformanceCounter(&freq);
        QPart1 = freq.QuadPart;

        /*dst.upload(src_host);*/
        dst1.upload(imread("d:\\CUDA\\train.png", 1));

        QueryPerformanceCounter(&freq);
        QPart6 = freq.QuadPart;
        dfMinus = (double)(QPart6 - QPart1);
        dfTim = 1000 * dfMinus / dfFreq;
        printf("Read image running time 2 is %.2f ms\n\n",dfTim);

        // PART3~ GPU code~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        // gpuimage From color image to grayscale image.
        QueryPerformanceCounter(&freq);
        QPart1 = freq.QuadPart;

        GpuMat gimg_gray;
        gpu::cvtColor(gimg_src,gimg_gray,CV_BGR2GRAY);

        QueryPerformanceCounter(&freq);
        QPart6 = freq.QuadPart;
        dfMinus = (double)(QPart6 - QPart1);
        dfTim = 1000 * dfMinus / dfFreq;
        printf("GPU RGB2GRAY running time is %.2f ms\n\n",dfTim);

        // PART4~CPU code(again)~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

        // gpuimage From color image to grayscale image.
        QueryPerformanceCounter(&freq);
        QPart1 = freq.QuadPart;
        Mat img_gray2;
        cvtColor(img_src,img_gray2,CV_BGR2GRAY);
        BOOL i_test=QueryPerformanceCounter(&freq);
        printf("%d \n",i_test);
        QPart6 = freq.QuadPart;
        dfMinus = (double)(QPart6 - QPart1);
        dfTim = 1000 * dfMinus / dfFreq;
        printf("CPU RGB2GRAY running time is %.2f ms\n\n",dfTim);

        cvWaitKey();
        getchar();
        return 0;
    }

12
GPU并不是普遍“慢”的。然而,主机和设备之间的内存传输非常缓慢。只有在你能将非常大、高度并行化的计算转移至设备时,GPU计算才有意义。 - Kerrek SB
还应该检查http://answers.opencv.org/question/1670/huge-time-to-upload-data-to-gpu/#1676 - Sam
如果您在GPU优化的函数中传递未分配的GpuMat,则会进行GPU内存分配。为了避免这种情况,您应该在使用函数之前预先分配适当大小的内存。 - geek
5个回答

32
上面大部分答案都是错误的。它变慢20000倍的原因当然不是因为“CPU时钟速度更快”和“它必须将其复制到GPU”(接受的答案)。这些是因素,但是如果仅因为后者说20000倍的性能差异就太荒谬了。作者知道有些不对劲。解决方法:问题在于CUDA需要初始化! 它总是会为第一张图像初始化,并且通常需要1-10秒钟的时间,具体取决于木星和火星的对齐情况。现在试一下。计算两次,然后计时。在这种情况下,您可能会发现速度在相同数量级内,而不是20000倍,那太荒谬了。你能做一些关于这个初始化的事情吗?没有,至少我不知道。这是一个障碍。 编辑:我刚刚重新阅读了这篇文章。你说你正在笔记本电脑上运行。它们通常具有糟糕的GPU和具有良好增强功能的CPU。

3
这应该是被接受的答案,因为它给出了观察到的性能问题的实际原因。@Tae已经在3年前指出了这一点,但在分析中不够明确。 - Ale

25

cvtColor并没有做很多工作,要让图像变成灰度图只需要取三个数的平均值。

在CPU上,cvtColor使用SSE2指令一次处理多达8个像素,如果你有TBB它会使用所有核心/超线程,CPU的时钟速度是GPU的10倍,最后你不需要将数据复制到GPU再返回。


谢谢您的回答!在这段代码中,我使用CPU对RGB2GRAY进行了两次处理(同样的代码,一次是在GPU代码之前,另一次是在GPU之后)。结果显示第二次比第一次慢得多。但它们是相同的代码!您能否给我一些提示? - David Ding
2
这可能也与创建CUDA上下文需要时间有关。我不知道您如何评估代码的时间。 - Amir Zadeh

9

尝试多次运行...

-----------摘自http://opencv.willowgarage.com/wiki/OpenCV%20GPU%20FAQ 性能表现

为什么第一次调用函数很慢?

这是因为初始化开销。在第一次GPU函数调用时,Cuda Runtime API 会隐式初始化。此外,会在第一次使用时为您的显卡编译一些GPU代码(即时编译)。因此,在进行性能测试时,需要进行虚拟函数调用,然后再执行时间测试。

如果应用程序仅需要运行一次GPU代码,则可以使用编译缓存,该缓存在多个运行中保持不变。有关详细信息,请阅读 nvcc 文档 (CUDA_DEVCODE_CACHE 环境变量)。


1

cvtColour是一个小操作,通过在GPU上执行该操作获得的任何性能提升都被主机(CPU)和设备(GPU)之间的内存传输时间所压倒。最大程度地减少内存传输的延迟是任何GPU计算的主要挑战。


0
你有什么GPU?检查计算兼容性,可能是原因。

https://developer.nvidia.com/cuda-gpus

这意味着对于具有CC 1.3和2.0的设备,二进制映像已准备就绪。对于所有更新的平台,1.3的PTX代码将被JIT编译为二进制映像。对于具有CC 1.1和1.2的设备,将JIT编译1.1的PTX。对于具有CC 1.0的设备,没有可用的代码,函数会抛出异常。对于首先执行JIT编译的平台,运行速度较慢。

http://docs.opencv.org/modules/gpu/doc/introduction.html


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接