CUDA学习笔记(LESSON1/2)

时间:2024-04-03 15:56:46

最近在看视频拼接的代码,师兄说要用CUDA加速,于是开始学习CUDA编程,课程链接:UdacityCS344


CUDA线程架构

CUDA学习笔记(LESSON1/2)

CUDA学习笔记(LESSON1/2)

CUDA架构由Grid、Block、Thread组成。

threadIdx代表一个block内线程索引值,在不同线程内该索引值都不同,最多存在三维,用.x、.y、.z表示

blockDim代表一个block内的线程总数,最多存在三维

blockIdx代表一个grid内块的索引值

gridDim代表一个grid内的块总数

 

CUDA程序架构

下图展示了CUDA程序的架构,我们可以同时对CPU与GPU进行操纵,我们把CPU叫做Host,GPU叫做Device,我们在GPU上运行相同的串行程序,我们把这个程序叫做kernel。另外CPU与GPU不共用内存。

CUDA学习笔记(LESSON1/2)

 

 

通信模式

通信模式指的是kernel(内核程序)中输入与输出之间的关系,该关系分为四种:map(映射)、gather(收集)、scatter(散射)、stencil(模板)、transpose(转置)

map

CUDA学习笔记(LESSON1/2)

map描述的是输出与输入之间一对一的映射关系

gather

CUDA学习笔记(LESSON1/2)

gather描述的是一个输出由多个输入进行运算得到

 

scatter

CUDA学习笔记(LESSON1/2)

scatter描述的是一个输入参与多个输出的运算

 

stencil

CUDA学习笔记(LESSON1/2)

stencil是一种模板,代表输出由相邻元素以一种固定的方式合成。gather就是一种stencil。需要注意的是stencil与gather不同的是stencil一定是每一个输出都由相同的输入模式确定,而gather则不一定要求输入以相同的模式合成输出。例如如下代码的下面一行:

CUDA学习笔记(LESSON1/2)

由于有判断条件,因此不是所有输出都执行相同的操作,因此这种情况属于gather,而不是stencil

 

transpose

 

CUDA学习笔记(LESSON1/2)

转置操作实际上就是输入序列的重排

 

GPU硬件与内存模型

GPU功能

CUDA学习笔记(LESSON1/2)

当我们写完程序以后,GPU的工作就是把一个kernel内不同block分配给不同的SM(流处理器),只有当SM执行完其内所有block中的所有线程后,GPU才会给它分配新的block。用户无法操控哪个block分配给哪个SM,也不能操控哪个block先执行。但是GPU可以保证一个block内的所有thread是同时运行的,也可以确定当下一个kernel启动时,前一个kernel内的所有线程一定运行完毕。

 

线程同步

在对global memory操作的时候我们经常需要考虑的一个问题就是线程同步问题。当多个线程需要对同一个内存进行读写的时候,我们就需要用到synchronize(同步)的技术,其中一个重要的概念那就是barrier。barrier指程序中的一个节点,当thread到达这的时候就会暂停运行,直到所有thread都到达这一点,各个线程才会继续运行。而这个过程也就是所谓的线程同步。而我们之前谈到的不同kernel之间实际上存在隐式barrier来保证一个kernel所有thread运行完成以后才开启下一个kernel

CUDA学习笔记(LESSON1/2)

 

内存模型

CUDA学习笔记(LESSON1/2)

 

GPU的内存分为三个部分,local memory、shared memory与global memory。local memory相当于局部变量,只在本线程内可以访问,shared memory只在一个block内可以访问,而global memory相当于全局变量,在所有线程中都可以对其访问。而相应带来的结果是访问的速度有所不同,最快的是local memory,其次是shared memory,最慢是global memory,我们在做并行运算的时候希望能够让花在计算上的时间达到最大,而从内存读写的时间降到最低,因此,我们经常会把需要经常读写的数据从global memory中转移到shared memory中,这样能够大大的提高程序的效率。

 

合并存取模式

合并存取模式(coalesce access pattern)也是一种减少读写时间的方法,其主要用于从global memory读取数据的时候使用。

CUDA学习笔记(LESSON1/2)

这个概念在图中已经描述的很清楚了,当thread从连续的内存中读写数据的时候效率是最高的,因为内存的读写模式是一个chunk,如果读写位置越分散,意味着需要的chunk越多,效率也就越低。

 

原子内存操作

原子内存操作(atomic memory operation)是为了解决多个线程对同一块内存区域访问时产生的冲突问题。这个操作跟线程同步的区别是:

线程同步是解决多个线程在不同时间段内对同一块内存进行读写操作产生的冲突。这很容易理解,我们先对一块内存进行读操作,之后进行线程同步以保证所有读操作都进行完毕,再进行写操作,这样就避免了写操作覆盖了原来的内容的问题。

而原子内存操作是解决在同一时间点多对线程需要访问同一块内存而进行的操作,具体方法就是将并行的线程串行化,使得同一时间点只有一个线程能对同一块内存进行访问。

而原子内存操作的局限就是它会比普通的读写操作要慢,但是很多时候为了解决冲突我们不得不采用这种操作。

原子的意思是将read-modify-write三个操作整合成一个操作,称其为原子操作,在原子操作的进行过程中其他线程不能对这块内存进行修改

 

线程发散

线程发散(thread divergence)指的是在kernel中存在条件语句或循环语句,使得不同线程经过的路径不同,我们把这种情况叫做发散。它的缺点就是效率降低了,因为运行快的线程不得不停下来等待运行慢的线程以保证程序的收敛。因此我们应尽量避免程序的发散

CUDA学习笔记(LESSON1/2)

条件语句的发散

CUDA学习笔记(LESSON1/2)

循环语句的发散

CUDA学习笔记(LESSON1/2)

不同线程循环次数不同导致效率降低