CUDA学习,第一个kernel函数及代码讲解

  • Post author:
  • Post category:其他



前一篇CUDA学习,我们已经完成了编程环境的配置,现在我们继续深入去了解CUDA编程。本博文分为三个部分,第一部分给出一个代码示例,第二部分对代码进行讲解,第三部分根据这个例子介绍如何部署和发起一个kernel函数。



一、代码示例
#include< stdio.h>  
#include "cuda_runtime.h"  
#include "device_launch_parameters.h"  
#include 
   
   
    
    
#include 
    
    
     
     

// Simple utility function to check for CUDA runtime errors
void checkCUDAError(const char *msg);

// Part 3 of 5: implement the kernel
__global__ void myFirstKernel(int *d_a)
{
	int i=blockIdx.x*blockDim.x+threadIdx.x;
	d_a[i]=1000 * blockIdx.x + threadIdx.x;
}


// Program main

int main( int argc, char** argv) 
{
    // pointer for host memory
    int *h_a;

    // pointer for device memory
    int *d_a;

    // define grid and block size
    int numBlocks = 8;
    int numThreadsPerBlock = 8;

    // Part 1 of 5: allocate host and device memory
    size_t memSize = numBlocks * numThreadsPerBlock * sizeof(int);
    h_a = (int *) malloc(memSize);
    cudaMalloc((void**)&d_a,memSize);

    // Part 2 of 5: configure and launch kernel
    dim3 dimGrid(numBlocks,1,1);
    dim3 dimBlock(numThreadsPerBlock,1,1);
    myFirstKernel<<
     
     
      
      >>(d_a);

    // block until the device has completed
    cudaThreadSynchronize();

    // check if kernel execution generated an error
    checkCUDAError("kernel execution");

    // Part 4 of 5: device to host copy
    cudaMemcpy(h_a,d_a,memSize,cudaMemcpyDeviceToHost);

    // Check for any CUDA errors
    checkCUDAError("cudaMemcpy");

    // Part 5 of 5: verify the data returned to the host is correct
    for (int i = 0; i 
      
      
     
     
    
    
   
   



二、代码解说


void checkCUDAError(const char *msg);


申明一个函数,用于检测CUDA运行中是否出错。


__global__ void myFirstKernel(int *d_a)
{
	int i=blockIdx.x*blockDim.x+threadIdx.x;
	d_a[i]=1000 * blockIdx.x + threadIdx.x;
}


kernel函数,blockIdx.x表示block在x方向的索引号,blockDim.x表示block在x方向的维度,threadIdx.x表示thread在x方向的索引号。


这里也许你会问,为什么在x方向?难道还有其他方向?


对的,grid

可以是一维、二维,

block可以是一维、二维和三维的。一个grid里包含多个block,一个block里也包含多个thread,可参考下图。





从而,i是每个thread的索引号,也许你会问为什么不直接是

threadIdx.x呢?



因为每个block里的

threadIdx.x都是从0到

blockDim.x(假设

block是一维的

),那么不同的

block里

threadIdx.x会出现相同的值,我们就不知道该调用那个thread来执行,因为用

threadIdx.x+

blockIdx.x*

blockDim.x表示每个thread的索引号,这样就是唯一的了。举个例子,假设grid和block都是一维的,

blockDim.x=8,

threadIdx.x从0到7,

blockIdx.x也是

从0到7,那么i就是0,1,2,3,4,5,6,7,8,9,10,11...63,从而保证了每个thread索引号的唯一性。





































// pointer for host memory
    int *h_a;

    // pointer for device memory
    int *d_a;

    // define grid and block size
    int numBlocks = 8;
    int numThreadsPerBlock = 8;


申请两个指针

*


h_a和

*


d_a


,分别指向host内存和device内存,host是指主程序cpu内存,device是指gpu内存(global memory)。并定义一个grid里block数(即

numBlocks)和每个block里thread数

numThreadsPerBlock。































size_t memSize = numBlocks * numThreadsPerBlock * sizeof(int);
h_a = (int *) malloc(memSize);
cudaMalloc((void**)&d_a,memSize);


分配host和device内存,

cudaMalloc


((


void


**


)


&


d_a


,


memSize


)是给device中

d_a分配

memSize字节的内存,为什么前面有

(


void


**


)


&呢?因为这个内存分配要通过cpu传给gpu。









































dim3 dimGrid(numBlocks,1,1);
dim3 dimBlock(numThreadsPerBlock,1,1);
myFirstKernel<<
    
    
     
     >>(d_a);

    
    


部署并发起kernel函数,

k



ernel函数是跑在gpu上的那段程序,即我们之前申明的

myFirstKernel函数。部署:

dimGrid


(


numBlocks


,


1


,


1


),一维的grid,里面包含

numBlocks个block;

dimBlock


(


numThreadsPerBlock


,


1


,


1


),grid里每个block都是一维的,每个block有

numThreadsPerBlock个thread。发起kernel函数:

myFirstKernel


<<<


dimGrid


,


dimBlock


>>>


(


d_a


),第一个是函数名,括号里是kernel的部署,后面一个是函数的参数。

























































cudaThreadSynchronize();

checkCUDAError("kernel execution");


同步并检查kernel函数运行是否出错。为什么要同步呢?因为每个thread运行的时间是不一样的,只有等所有线程都跑完了,我们才做下一件事。这会造成运行的性能降低,但是是必要的。




















































 cudaMemcpy(h_a,d_a,memSize,cudaMemcpyDeviceToHost);



cudaMemcpy函数完成数据在host和device之间的传输,第一个参数是传输的目标,第二个参数是传输的源数据,第三个参数传输的数据量,第四个参数是传输的方向,这里是从device传到host。































 checkCUDAError("cudaMemcpy");


检查cudaMemcpy函数运行是否出错。































for (int i = 0; i 
    
    



检查从device传回的数据是否正确。






























cudaFree(d_a);

free(h_a);


释放host和device内存。






























void checkCUDAError(const char *msg)
{
    cudaError_t err = cudaGetLastError();
    if( cudaSuccess != err) 
    {
        fprintf(stderr, "Cuda error: %s: %s.\n", msg, cudaGetErrorString( err) );
        exit(-1);
    }                         
}


检查CUDA函数是否运行正确的函数。


三、

部署和发起一个kernel函数



如上述,对kernel函数先申明,在函数体中实现线程的算法,即:




__global__ void myFirstKernel(int *d_a)
{
	int i=blockIdx.x*blockDim.x+threadIdx.x;
	d_a[i]=1000 * blockIdx.x + threadIdx.x;
}


然后部署和发起kernel函数,即:




    // define grid and block size
    int numBlocks = 8;
    int numThreadsPerBlock = 8;

    // Part 1 of 5: allocate host and device memory
    size_t memSize = numBlocks * numThreadsPerBlock * sizeof(int);
    h_a = (int *) malloc(memSize);
    cudaMalloc((void**)&d_a,memSize);

    // Part 2 of 5: configure and launch kernel
    dim3 dimGrid(numBlocks,1,1);
    dim3 dimBlock(numThreadsPerBlock,1,1);
    myFirstKernel<<
     
     
      
      >>(d_a);

     
     


综上,我们完成代码的讲解,部署和发起一个kernel函数


。想必你现在应该对CUDA有了较为深入的了解了!是吧?




版权声明:本文为helei001原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。