cuda编程过程心得

目录


* 详情
* 总结

详情

我写东西比较随心,如果只想看干的,可以直接看 总结

项目场景需要,处理图像,效果出来了,但是时间上一直得不到保障,于是打算改写为cuda并行。
由于cuda程序和c语言很像,而且我以前也了解过相关cuda方面的例子,所以编写一个cuda程序还是相对比较容易的,主要是将逻辑表达清楚。
然后进入语法调试阶段,语法调试相对比较轻松,nvcc编译器还是相对比较人性化,报错在哪一行也会提示。折腾了半个小时,将语法修正完毕。
进入动态运行调试阶段,这个阶段是比较痛苦的,由于在cuda程序中是不能输出调试信息的,所以唯一的调试信息是在cuda调用后出现的获取的,这里要注意一下,一定要获取cuda的运行状态,否则连在哪儿、报的撒子错、运行的结果是啥就不知道了。 后来我还是找到了问题,原来是我将主机的指针传到cuda设备上了,cuda设备和主机完全是两套指针系统,不死才怪,找到原因,做了简单的修改就行。

再然后就进入逻辑调试阶段,这个阶段完全是蒙的,不能输出中间变量,结果就是莫名奇妙的出来的,所以我建议大家先用c语言在主机上将结果运行正常了,在到cuda上做最后的调试,否则就算历尽千辛万苦,最后死到逻辑调试,真的是欲哭无泪呀。

其实cuda程序只需做少量修改就可以在c环境下运行,比如下面cuda程序

#include <stdio.h>
#include <unistd.h>
#include <opencv2/opencv.hpp>
#include <stdlib.h>

#ifndef checkCudaErrors
#define checkCudaErrors(err)  __checkCudaErrors (err, __FILE__, __LINE__)
inline void __checkCudaErrors(cudaError_t err, const char *file, const int line)
{
    if (cudaSuccess != err)
    {
        fprintf(stderr, "checkCudaErrors() Driver API error = %04d \"%s\" from file <%s>, line %i.\n",
                err, cudaGetErrorString(err), file, line);//getCudaDrvErrorString
        exit(EXIT_FAILURE);
    }
}
#endif

//一个简单的cuda程序, 图片的值超出上限就置0
__global__ void up_zero(unsigned char *src_, unsigned char *out_, int w, int thresh){
    int ind_x = blockIdx.x * blockDim.x + threadIdx.x;
    int ind_y = blockIdx.y * blockDim.y + threadIdx.y;

    unsigned char value = src_[w*ind_y+ind_x];
    if(value > thresh){
        out_[w*ind_y+ind_x] = 0;
    }else{
        out_[w*ind_y+ind_x] = value;
    }
	return;
}

int main(int argc, char **argv){ 
    int thresh = 30; //阈值
    
    cv::Mat m = cv::imread(argv[1]);
    if (m.empty())
    {
        printf("error load image!\n");
        return -1;
    }

    cudaEvent_t start, stop; //cuda事件,cuda开始和运算结束事件
    checkCudaErrors(cudaEventCreate(&start));
    checkCudaErrors(cudaEventCreate(&stop));
    checkCudaErrors(cudaEventRecord(start, 0));csdn

    void *pcuParai, *pcuParao;
    checkCudaErrors(cudaMalloc( (void**)&pcuParai,  m.rows*m.cols*sizeof(unsigned char)));
    checkCudaErrors(cudaMalloc( (void**)&pcuParao,  m.rows*m.cols*sizeof(unsigned char)));

    checkCudaErrors(cudaMemcpy( pcuParai, m.data, m.rows*m.cols*sizeof(int), cudaMemcpyHostToDevice));

    dim3 grid2(m.cols, m.rows,  1), block2(1, 1, 1); // dim3: 三维维度, 参数分别是 x, y, z
    up_zero<<<grid2, block2 >>>((unsigned char*)pcuParai, (unsigned char*)pcuParao, m.cols, thresh); //这就是调用了,简单吧

    checkCudaErrors(cudaEventRecord(stop, 0));
    checkCudaErrors(cudaEventSynchronize(stop)); //同步, 计算完成后返回

    unsigned char *t_data = (unsigned char*)malloc(m.rows*m.cols*sizeof(unsigned char));
    checkCudaErrors(cudaMemcpyAsync(t_data, pcuParao, m.rows*m.cols*sizeof(unsigned char), cudaMemcpyDeviceToHost,0)); //等gpu运算同步完后,将gpu的数据拷贝到cpu中

    checkCudaErrors(cudaFree( pcuParai )); //释放指针
    checkCudaErrors(cudaFree( pcuParao ));

    float elapsedTime;
    checkCudaErrors(cudaEventElapsedTime(&elapsedTime, start, stop)); //获取gpu运算时间
    printf("elapsedTime = %.5f\n",elapsedTime);

    checkCudaErrors(cudaEventDestroy(start)); //释放资源
    checkCudaErrors(cudaEventDestroy(stop));

    int sz[2] = {m.rows, m.cols};
    cv::Mat m_tz(2,sz, CV_8UC1, t_data);  //将内存的数据转换为mat变量
    cv::imshow("show", m_tz); //显示

    cv::waitKey(0);

    free(t_data); //释放指针
    return 0;
}

可以改写为:

#include <stdio.h>
#include <unistd.h>
#include <opencv2/opencv.hpp>
#include <stdlib.h>

//一个简单的cuda程序, 图片的值超出上限就置0
void up_zero(unsigned char *src_, unsigned char *out_, int w, int thresh, int id_x, int id_y)
{
    int ind_x = id_x;
    int ind_y = id_y;

    unsigned char value = src_[w * ind_y + ind_x];
    if (value > thresh)
    {
        out_[w * ind_y + ind_x] = 0;
    }
    else
    {
        out_[w * ind_y + ind_x] = value;
    }
    return;
}

int main(int argc, char **argv)
{
    int thresh = 30; //阈值
    cv::Mat m = cv::imread(argv[1]);
    if (m.empty())
    {
        printf("error load image!\n");
        return -1;
    }
    unsigned char *t_data = (unsigned char *)malloc(m.rows * m.cols * sizeof(unsigned char));
    for (int j = 0; j < m.rows; j++) //rows是行数,循环第几行, 也就是y
    {
        for (int i = 0; i < m.cols; i++) //cols是列数,i循环第几列, 也就是x
        {
            up_zero((unsigned char *)m.data, (unsigned char *)t_data, m.cols, thresh, i, j); //这就是调用了,简单吧
        }
    }
    int sz[2] = {m.rows, m.cols};
    cv::Mat m_tz(2, sz, CV_8UC1, t_data); //将内存的数据转换为mat变量
    cv::imshow("show", m_tz);             //显示

    cv::waitKey(0);
    free(t_data); //释放指针
    return 0;
}

通过这个意思,可以快速的将cuda程序转换为c语言程序,用i和j循环来模拟并行运算。虽然慢一点,但还是可以添加中间量的打印信息来排查程序中的逻辑bug。


总结

其实cuda编程比较方便,语法和c语法是差不多的。

主要注意以下几点:

  1. 在cuda的内存和主机内存要严格分开,我们知道地址其实就是门牌号,主机的门牌号往往比较多,拿主机的门牌号到cuda中找,往往就会崩溃。
  2. cuda程序调试:
    a. 程序用c语言调试好了在放到cuda中,这样可以减少逻辑报错。
    b. 程序运行报错的时候使用注释的方法,一点一点的放出来, 如果逻辑报错,就用上面a方法换成c语言来循环,打印运行的状态。
  3. cuda的并行调用,就是三个重叠的<>之间的各个变量要清楚,否则可能并行不起来。可以看看cuda学习2-block与thread数量的选取

本来说写一个简单的例程,结果,搞出来的程序好像还可以编译,哈哈,都是模板和套路,所以大家不要怕cuda编程,有c的基础,cuda编程其实很像,上手很快的。我将上面的代码做了一份csdn下载,里面也做了简单的说明。

而且官方也做了很多的开发程序和API,如果熟悉了,可以大大缓解我们的开发的工作。

最后说一句什么来着,欢迎交流。

猜你喜欢

转载自blog.csdn.net/u012939880/article/details/92799326
今日推荐