一、CUDA结构

硬件:GPU(Graphics Processing Unit)   SM(Streaming Multiprocessor)     SP(Streaming Processor)

软件:Grid   Blcok   Thread

每个GPU由若干SM组成,每个SM由若干SP加上Register和shared memory组成,SP是真正执行线程的单元,这是GPU的硬件架构。

CUDA提供GBT逻辑结构,大致对应GSM

除此之外,GPU的调度单位是warp,基本上是32个Thread组成一个warp,由warp scheduler进行调度, 每个warp内的线程SIMT(Single Instruction Multi Thread)

 

二、CUDA基本语法

__global__    声明一个kernel函数 , <<<block, thread>>>  调用kernel函数, block,thread可以是二维的(用dim3传入, dim3的结构为dim3(x, y, z))

—divece__   声明一个函数,表示是在kernel中调用的

cudaMalloc(pointer, size);

cudaMemcpy(dest, src, size, direction)

cudaFree() 

cudaDevicePro结构体

并形编程时offset的计算,一维二维不同

** 显存和主存的指针可以相互传递,但显存的读写只能在GPU上操作,内存的读写只能在CPU上操作,即设备上不能读写主存,CPU上不能读写设备内存 **

cudaSetDevice(i)有多个GPU时,选择一个i

cuda的好多函数都会返回一个cudaError_t类型的状态,如果是cudaSuccess,表示成功执行

 

 注意:在执行了kernel函数后没有任何反应记得在kernel函数后添加cudaDeviceReset()指令,注意该指令必须要在kernel函数后,且在把设备内存中的内容复制到主存后。

三、cuda线程同步

__syncthreads()同步一个block内的线程,使block内的所有线程的__syncthreads()前面的代码全部运行完毕,才运行后面的代码,注意该同步指令不能放入分支结构中,否则会死锁永久等待。

注意:__syncthreads()只能同步一个block内的线程,无法同步block间的线程。

 

四、优化性能

__shared__   声明shared memory, 每个block 共享shared memory,可读可写,芯片内部内存,相当于高速缓存。

__constant__    声明constant memory,一般GPU会将显存分出64KB的constant memory,read-only,合理的使用constant memory有助于提高CUDA程序性能

注意:constant memory 只能静态分配,无需释放,其大小要在编译的时候确定, 并且要声明为全局。

原因:广播,half-warp thread 读同一个constant memory地址的时,只产生一次读操作 ,这样只占1/16的带宽(注意这个提升很大,因为GPU内部的处理单元很多,内存带宽已满足不了运算能力,the bottleneck is bandwidth)

    cache,第一次读后,硬件会cache the constant data to GPU

duoble-edged sword:half-warp thread 都读一个constant memory,好剑,但若不同,则这16条thread读constant memory 将串行化,如果在global memory中即使不同也是并行的。

注意:用这个cudaMemcpyToSymbol(dist, src, size)拷贝constant memory

 

纹理内存,同常量内存有点像。

 

五、cuda事件

cudaEvent_t  start, stop;

cudaEventCreate(&start);  cudaEventCreate(&stop);

cudaEventRecord(start, 0);     cudaEventRecord(stop, 0);

cudaEventSynchronize(stop);

float elapseTime;

cudaEventElapsedTime(&elapseTime, start, stop);

cudaEventDestroy(start);        cudaEventDestroy(stop);

注意:cudaEvent is implemented directly on the GPU, it is not suitable to timing mixtures of divece and host code.

 

六、原子性

atomicAdd(&, value);

由于计算机中不满足浮点数加减法的结合率,故只提供了整数的atomicAdd()原子操作。但可以利用提供的对于整数的原子操作自己实现一个互斥锁,来实现临界资源的互斥访问。

struct Lock {
    int *mutex;
    Lock( void ) {
        HANDLE_ERROR( cudaMalloc( (void**)&mutex,
                              sizeof(int) ) );
        HANDLE_ERROR( cudaMemset( mutex, 0, sizeof(int) ) );
    }

    ~Lock( void ) {
        cudaFree( mutex );
    }

    __device__ void lock( void ) {
        while( atomicCAS( mutex, 0, 1 ) != 0 );
    __threadfence();
    }

    __device__ void unlock( void ) {
        __threadfence();
        atomicExch( mutex, 0 );
    }
};

【不确定】该互斥好像只能在块间有效执行,在块内线程之间就不行了。

 

七、流

前面的讨论的并行是相同任务不同数据的并行,流是不同任务间的并行(类似于CPU)

1、页锁定主机内存

cudaHostAlloc((void**)&add, ByteSize, cudaHostAllocDefault)分配固定内存,即不可分页,不可交换至磁盘(在释放之前),不可被其他程序占用

malloc(ByteSize)分配标准的可分页内存,可交换、可被占用

采用cudaHostAlloc分配的内存需要用cudaHostFree(add)释放,但还是可以用cudaMemcpy(),并且速度比用malloc()快

使用页锁定内存的一个好处:

  • 加快主机内存和设备内存间的复制操作。由于设备内存和主机内存之间的交换用DMA来完成,不需要CPU的参与,故当使用可分页内存时,由于可能存在某页被换到磁盘中延缓DMA控制器的操作,故对于分页内存与设备内存交换时,系统先是申请了一块页锁定内存,先将内容拷贝进来,再将其与设备内存进行交换。即:可分页内存<->页锁定内存<->设备内存

2、流

创建流

cudaStream_t  stream;

cudaStreamCreate(&stream);

流就好比一个任务对列,每个流中的任务串行执行,不同的流之间可以并行执行,但并不能全部并行,只能在内存拷贝和执行核函数上面并行

cudaMemcpyAsync(dest, src, size, direction, stream) 不同于cudaMemcpy和memcpy,后面两个是同步的,即函数执行完了,内存拷贝也就完成了,前者是一个异步方式,只是在流中提出了一个请求,并不一定完成了。

kernel<<<block, thread, 0, stream>>>()核函数也要指定相应的流变成异步执行,第三个暂时未知

所以最后需要一个同步机制来等待流中的任务完成cudaStreamSynchronize(stream)

最后释放流cudaStreamDestroy(stream)

使用页锁定内存的另一个好处:

  • 异步

下面给个两个流的并行过程:

流1:HostToDevice      kernel                DeviceToHost

流2:                            HostToDevice    kernel                         DeviceToHost

 

八、动态并行

前面讨论的并行是在主机函数调用kernel函数,但在kernel函数中能否继续调用kernel,cuda从某一版本开始后开始支持这一机制,称为动态并行。

 

九、注意点

  • 在GPU中相邻线程访问相邻内存要比同一线程访问相邻内存的速度快。

 


 

参考:《cuda by exemple》

Posted on 2017-10-15 13:41  小小旅行商  阅读(394)  评论(0编辑  收藏  举报