赞
踩
这篇文章写的特别好,https://blog.csdn.net/xiaohu2022/article/details/79599947,我基本就是参考这篇文章
最难理解的部门就是Grid和Block的概念,具体关系参见下图
GPU之所以处理图片速度快,就是因为可以把图片的每一块区域分配给一个Thread来处理,每个Thread只负责处理图像的一块区域,当每个Thread都结束的时候,整张图就处理完成了,避免了大量for循环的使用
Grid和Block实际上组成了一张大网,当这张大网在图片上“扫一遍”之后,整张图片也就处理完了。尤其是当图片尺寸比较小,GPU资源丰富时,更是一次性就可以扫完。
要获取每个Thread对应的位置,可以使用
- int row = threadIdx.y + blockIdx.y * blockDim.y;
- int col = threadIdx.x + blockIdx.x * blockDim.x;
举个简单例子,假如我的图片大小是12*12,如果对这144个像素点,每个像素点做归一化,且一次完成,则可以设置
- dim3 gridSize(3, 3);
- dim3 blockSize(4, 4);
这样实际就有12*12个Thread,每个Thread处理一个像素值,一次就完成了。
当输入图片尺寸很大,无法一次完成时,每个Thread就需要处理多个像素点,这时就需要“stride”了,比如此时图片大小变为24*24,gridSize和blockSize的值不变,则此时每个Thread需要处理4个像素点,比如处理(0,0)点的Thread,还要处理(0,12),(12,0),(12,12)这几个点。
下面例子是我自己编写的第一个CUDA代码,输入为一张1920*1080的热力图,输出为这张图的局部极值点,即改点的值比上下左右四个点的值都大。首先定义kernel_fun,然后在host上采用kernel_fun<<< grid, block >>>(prams...)的形式调用
- // 获取矩阵A的(row, col)元素
- __device__ float getElement(float *A, int height, int width, int row, int col)
- {
- return A[row * width + col];
- }
-
- // 为矩阵A的(row, col)元素赋值
- __device__ void setElement(bool *A, int height, int width, int row, int col, bool value)
- {
- A[row * width + col] = value;
- }
-
- __global__ void getPeak(float *d_in, int height, int width, float thre, bool *d_out)
- {
- int row = threadIdx.y + blockIdx.y * blockDim.y;
- int col = threadIdx.x + blockIdx.x * blockDim.x;
-
- int stride_row = blockDim.y * gridDim.y;
- int stride_col = blockDim.x * gridDim.x;
-
- int max_iter_row = (height + stride_row - 1) / stride_row;
- int max_iter_col = (width + stride_col - 1) / stride_col;
-
- for(int iter_row = 0; iter_row <= max_iter_row ; iter_row++)
- {
- col = threadIdx.x + blockIdx.x * blockDim.x;
- for (int iter_col = 0; iter_col <= max_iter_col; iter_col++)
- {
- //setElement(d_out, height, width, row, col, 1);
- if (row - 1 <= 0 || col - 1 <= 0) continue;
- if (row + 1 >= height || col + 1 >= width) continue;
-
- float map_cen_val = getElement(d_in, height, width, row, col);
- if (
- (map_cen_val >= thre) &&
- (map_cen_val >= getElement(d_in, height, width, row - 1, col)) &&
- (map_cen_val >= getElement(d_in, height, width, row + 1, col)) &&
- (map_cen_val >= getElement(d_in, height, width, row, col - 1)) &&
- (map_cen_val >= getElement(d_in, height, width, row, col + 1))
- )
- {
- setElement(d_out, height, width, row, col, true);
- }
-
- col += stride_col;
- }
- row += stride_row;
- }
- }
Host上的调用方法为
- void findPeakPose(float* input_data, int height, int width, float thre, std::vector<Point>& peak_points)
- {
- int nBytes = width * height * sizeof(float);
- int nBytes_output = width * height * sizeof(bool);
- // 申请托管内存
- float *d_in;
- bool *d_out;
- cudaMalloc((void**)&d_in, nBytes);
- cudaMalloc((void**)&d_out, nBytes_output);
-
- // 将host数据拷贝到device
- cudaMemcpy((void*)d_in, (void*)input_data, nBytes, cudaMemcpyHostToDevice);
-
- // 定义kernel的执行配置
- dim3 blockSize(32, 32);
- dim3 gridSize(5, 5);
-
- // 执行kernel
- getPeak<<<gridSize, blockSize>>>(d_in, height, width, thre, d_out);
-
- // 同步device 保证结果能正确访问
- cudaDeviceSynchronize();
-
- bool* output_data = (bool*)malloc(nBytes_output);
- cudaMemcpy((void*)output_data, (void*)d_out, nBytes_output, cudaMemcpyDeviceToHost);
-
- for(int i = 0; i < width * height; i++)
- {
- if (output_data[i])
- {
- Point p;
- p.width = i%1920;
- p.height = i/1920;
- peak_points.push_back(p);
- }
- }
-
- cudaFree(d_in);
- cudaFree(d_out);
- free(output_data);
- }
简单说明一下__device__ __global__等函数的意义(引用自https://blog.csdn.net/xiaohu2022/article/details/79599947)
__global__
:在device上执行,从host中调用(一些特定的GPU也可以从device上调用),返回类型必须是void
,不支持可变参数参数,不能成为类成员函数。注意用__global__
定义的kernel是异步的,这意味着host不会等待kernel执行完就执行下一步。__device__
:在device上执行,单仅可以从device中调用,不可以和__global__
同时用。__host__
:在host上执行,仅可以从host上调用,一般省略不写,不可以和__global__
同时用,但可和__device__
,此时函数会在device和host都编译。Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。