Caffe源码学习3-CUDA编程

CUDA(Compute Unified Device Architecture)的简称,是由NVIDIA公司创立的基于他们公司生产的图形处理器GPUs(Graphics Processing Units,可以通俗的理解为显卡)的一个并行计算平台和编程模型。

通过CUDA,GPUs可以很方便地被用来进行通用计算(有点像在CPU中进行的数值计算等等),相比于CPU,GPUs的FLOPS(float-point Operations Per Second)是CPU的数倍。在没有CUDA之前,GPUs一般只用来进行图形渲染(如通过OpenGL,DirectX)。开发人员可以通过调用CUDA的API,来进行并行编程,达到高性能计算目的。而且并行计算由大型集群扩展到了普通显卡,使得用户只需要一台带有Geforce显卡的计算机就能运行较大规模的并行处理程序。

一、主要概念和名称

  • 主机(Host): 将CPU及系统的内存(内存条)称为主机。
  • 设备(Device): 将GPU及GPU本身的显示内存称为设备。

Host 端是指在 CPU 上执行的部分,而 Device 端则是GPU在上执行的部分。Device 端的程序又称为 “Kernel”。通常 Host 端程序会将数据准备好后,复制到显卡的显存中,再由显示芯片执行 Device 端程序,完成后再由 Host 端程序将结果从显存中取回。

  • 线程(Thread): 显存执行操作最小单位,一般通过GPU的一个核进行处理。
  • 线程块(Block)
    1. 由多个线程组成
    2. 各block是并行执行的,block间无法通信,也没有执行顺序。
    3. 注意线程块的数量限制为不超过65535(硬件限制)。
  • 线程格(Grid): 由多个线程块组成(可以表示成一维,二维,三维,具体下面再细说)。

  • 线程束:在CUDA架构中,线程束是指一个包含32个线程的集合,这个线程集合被“编织在一起”并且以“步调一致”的形式执行。在程序中的每一行,线程束中的每个线程都将在不同数据上执行相同的命令。
  • Kernel:
    1. 在GPU上执行的函数通常称为核函数(Kernel)。
    2. 一般通过标识符global修饰,调用通过<<<参数1,参数2>>>,用于说明内核函数中的线程数量,以及线程是如何组织的。
    3. 以线程格(Grid)的形式组织,每个线程格(Grid)由若干个线程块(block)组成,而每个线程块又由若干个线程(thread)组成。
    4. 是以block为单位执行的。
    5. 在主机端(Host)代码中调用。
    6. 在调用时必须声明内核函数的执行参数。
    7. 在编程时,必须先为Kernel函数中用到的数组或变量分配好足够的空间,再调用Kernel函数,否则在GPU计算时会发生错误,例如越界或报错。

测试代码:test.cu

#include 
// 核函数声明,前面的关键字__global__  
__global__ void kernel( void ){
    printf("Hello World from the GPU!\n");
}  
int main(int argc, char* argv[]) {  
    //核函数的调用,注意<<<1,1>>>,第一个1,代表线程格里只有一个线程块;第二个1,代表一个线程块里只有一个线程。  
    kernel<<<1,1>>>();  
    cudaDeviceReset(); // 
    return 0;  
} 
nvcc -Wno-deprecated-gpu-targets -arch sm_20 test.cu -o test 
  • dim3数据类型:
    1. dim3是基于uint3定义的矢量类型,相当于由3个unsigned int型组成的结构体。uint3类型有三个数据成员unsigned int x; unsigned int y; unsigned int z;
    2. 可使用一维、二维或三维的索引来标识线程,构成一维、二维或三维线程块。
    3. dim3结构类型变量用于核函数调用的<<<,>>>中。
    4. dim3内置变量:
      • threadIdx,获取线程thread的ID索引;如果线程是一维的那么就取threadIdx.x,二维的还可以多取到一个值threadIdx.y,以此类推到三维threadIdx.z。
      • blockIdx,线程块的ID索引;同样有blockIdx.x,blockIdx.y,blockIdx.z。
      • blockDim,线程块的维度,同样有blockDim.x,blockDim.y,blockDim.z。
      • gridDim,线程格的维度,同样有gridDim.x,gridDim.y,gridDim.z。

注意:

  • 对于一维的block,线程的threadID == threadIdx.x。
  • 对于大小为(blockDim.x, blockDim.y)的二维 block,线程的threadID=threadIdx.x+threadIdx.y*blockDim.x。
  • 对于大小为(blockDim.x, blockDim.y, blockDim.z)的三维 block,线程的threadID=threadIdx.x+threadIdx.y*blockDim.x+threadIdx.z*blockDim.x*blockDim.y。
  • 对于计算线程索引偏移增量为已启动线程的总数。如stride = blockDim.x * gridDim.x; threadId += stride。

  • 函数修饰符:

    1. global,表明被修饰的函数在Device上执行,但在Host上调用。
    2. device,表明被修饰的函数在Device上执行,但只能在其他device函数或者global函数中调用。

二、GPU内存分类

  • 全局内存:通俗意义上的设备内存。
  • 共享内存
    1. 位置:设备内存。
    2. 形式:关键字shared添加到变量声明中。如shared float cache[10]。
    3. 目的:对于GPU上启动的每个线程块,CUDA C编译器都将创建该共享变量的一个副本。线程块中的每个线程都共享这块内存,但线程却无法看到也不能修改其他线程块的变量副本。这样使得一个线程块中的多个线程能够在计算上通信和协作。
  • 常量内存

    1. 位置:设备内存
    2. 形式:关键字constant添加到变量声明中。如constant float s[10];。
    3. 目的:为了提升性能。常量内存采取了不同于标准全局内存的处理方式。在某些情况下,用常量内存替换全局内存能有效地减少内存带宽。
    4. 特点:常量内存用于保存在核函数执行期间不会发生变化的数据。变量的访问限制为只读。NVIDIA硬件提供了64KB的常量内存。不再需要cudaMalloc()或者cudaFree(),而是在编译时,静态地分配空间。
    5. 要求:当我们需要拷贝数据到常量内存中应该使用cudaMemcpyToSymbol(),而cudaMemcpy()会复制到全局内存。
    6. 性能提升的原因:

      6.1. 对常量内存的单次读操作可以广播到其他的“邻近”线程。这将节约15次读取操作。(为什么是15,因为“邻近”指半个线程束,一个线程束包含32个线程的集合。)

      6.2. 常量内存的数据将缓存起来,因此对相同地址的连续读操作将不会产生额外的内存通信量。

  • 纹理内存

    1. 位置:设备内存
    2. 目的:能够减少对内存的请求并提供高效的内存带宽。是专门为那些在内存访问模式中存在大量空间局部性的图形应用程序设计,意味着一个线程读取的位置可能与邻近线程读取的位置“非常接近”。
    3. 纹理变量(引用)必须声明为文件作用域内的全局变量。
    4. 形式:分为一维纹理内存 和 二维纹理内存。
  • 固定内存

    1. 位置:主机内存。
    2. 概念:也称为页锁定内存或者不可分页内存,操作系统将不会对这块内存分页并交换到磁盘上,从而确保了该内存始终驻留在物理内存中。因此操作系统能够安全地使某个应用程序访问该内存的物理地址,因为这块内存将不会破坏或者重新定位。
    3. 目的:提高访问速度。由于GPU知道主机内存的物理地址,因此可以通过“直接内存访问DMA(Direct Memory Access)技术来在GPU和主机之间复制数据。由于DMA在执行复制时无需CPU介入。因此DMA复制过程中使用固定内存是非常重要的。
    4. 缺点:使用固定内存,将失去虚拟内存的所有功能;系统将更快的耗尽内存。
  • 原子性
    1. 概念:如果操作的执行过程不能分解为更小的部分,我们将满足这种条件限制的操作称为原子操作。
    2. 形式:函数调用,如atomicAdd(addr,y)将生成一个原子的操作序列,这个操作序列包括读取地址addr处的值,将y增加到这个值,以及将结果保存回地址addr。
      常用线程操作函数
      1. 同步方法__syncthreads(),这个函数的调用,将确保线程块中的每个线程都执行完__syscthreads()前面的语句后,才会执行下一条语句。
        使用事件来测量性能
      2. 用途:为了测量GPU在某个任务上花费的时间。CUDA中的事件本质上是一个GPU时间戳。由于事件是直接在GPU上实现的。因此不适用于对同时包含设备代码和主机代码的混合代码设计。
      3. 形式:首先创建一个事件,然后记录事件,再计算两个事件之差,最后销毁事件。如:

三、GPU Functions:

  • cudaMalloc()

    函数原型: cudaError_t cudaMalloc (void **devPtr, size_t size)。

    函数用法:与C语言中的malloc函数一样,在GPU中分配显存。

    Note:

    可以将cudaMalloc()返回的指向已分配地址空间的指针传递给在设备上执行的函数;
    可以在__device__代码中使用cudaMalloc()返回的指针进行设备内存读写操作;
    可以将cudaMalloc()返回的指针传递给在Host上执行的函数;
    不可以在主机代码中使用cudaMalloc()返回的指针进行Host的内存读写操作(即不能进行解引用)。
    
  • cudaMemcpy()

    函数原型:cudaError_t cudaMemcpy (void *dst, const void *src, size_t count, cudaMemcpyKind kind)。

    函数用法:与c语言中的memcpy函数一样,只是此函数在Host内存与GPU显存之间互相拷贝数据。

    函数参数:cudaMemcpyKind kind表示数据拷贝方向,如果kind赋值为cudaMemcpyDeviceToHost表示数据从Device显存拷贝到Host内存。

    与C中的memcpy()一样,以同步方式执行,即当函数返回时,复制操作就已经完成了,并且在输出缓冲区中包含了复制进去的内容。

    相应的有个异步方式执行的函数cudaMemcpyAsync(),这个函数详解请看下文流有关内容。

  • cudaFree()

    函数原型:cudaError_t cudaFree ( void* devPtr )。

    函数用法:与c语言中的free()函数一样,只是此函数释放的是cudaMalloc()分配的内存。

    #include   
    #include   
    __global__ void add( int a, int b, int *c ) {  
        *c = a + b;  
    }  
    int main( void ) {  
        int c;  int *dev_c;   
        cudaMalloc( (void**)&dev_c, sizeof(int) );  
        // 核函数执行  
        add<<<1,1>>>( 2, 7, dev_c );     
        cudaMemcpy( &c, dev_c, sizeof(int),cudaMemcpyDeviceToHost ) ;  
        printf( "2 + 7 = %d\n", c );   
        cudaFree( dev_c );  
    
        return 0;  
    }  
    
  • cudaGetDeviceCount: 获得计算能力大于等于1.0的GPU数量
  • cudaGetDeviceProperties: 获取设备GPU的属性

测试代码:test.cu

#include 
int main( void ) {
    cudaError_t cudaStatus;
    int num = 0;
    cudaDeviceProp prop;
    cudaStatus = cudaGetDeviceCount(&num);
    for(int i = 0;i
  • cudaSetDevice : 设置使用的GPU索引号,如果不设置默认使用0号GPU

    int gpuid = 0; cudaSetDevice(gpuid);

  • cudaGetDevice : 获得当前线程的GPU设备号

    int gpuid; cudaGetDevice(&gpuid);

  • cudaSetValidDevices : 设置多个device,len表示签名设备号数组的长度;
    cudaSetValidDevices(int &device_arr, int len);

参考: https://dragonfive.github.io/2017-08-16/cuda_programming/
参考: http://blog.csdn.net/baidu_24281959/article/details/52618644

你可能感兴趣的:(Caffe源码学习3-CUDA编程)