CUDA零碎
(1)线程束究竟是什么?
概念上讲,线程束是SM用SIMD方式所同时处理的工作粒度。当一个线程块被指定到一个SM上,线程块中的所有线程被分成了线程束,所以优化工作负载要考虑到线程束。或者这样说,我们从软体角度看线程,线程是独立的且并行执行,对硬体来说,却是被组织成32个线程为一堆的线程束来执行指令。因此,逻辑上我们每个线程块可以是一维,二维,三维线程,但是物理上都是以32个线程为单位的一维物理布局。
杰伦:线程块中线程数应该是32的倍数,不然最后那个没有被填满的线程束,会和其他填满的线程束一样消耗SM的资源,比如宝贵的寄存器。
(2)条件分支与线程束的关系
有一点非常重要,在一个线程束中所有线程在每个周期必须执行相同的指令,也就是说一个线程束中的所有线程,在面临条件分支的时候,集体选择同一条件执行,如果该线程束中有16个线程是选择a条件执行,另外16个是选择b条件执行,则实际的工作情况是:16个选择a在执行的时候将会禁用另外16个线程执行b,也就是串列执行不同的条件分支,这个对性能的影响是很恶劣的。举个栗子:
__global__ void kernel(float *c)
{
int index = threadIdx.x + blockIdx.x * blockDim.x;
float a =0,b=0;
if(index%2==0)
a = 100.0f;
else
b = 200.0f;
c[index] = a+b;
}
改进版(以线程束为单位,不再是以线程IDX奇偶,而是线程束的奇偶来分)
__global__ void kernel(float *c)
{
int index = threadIdx.x + blockIdx.x * blockDim.x;
float a =0,b=0;
if((index/wrapSize)%2==0)//线程束为偶数执行,这样可行的先决条件是,有一半线程执行a,有一半线程执行b
a = 100.0f;
else
b = 200.0f;
c[index] = a+b;
}
杰伦:在内核函数中,尽量避免数据分化,解决方法就是在条件分支处以线程束为单位。
(3)增大并行性来提高性能
实验表明,当我们的blockDim.x是32的倍数,能获增加载入吞吐量,而块数较多时(当然不能超过硬体限制),可以有更多活跃的线程束,这直接影响著可实现占用率,从而影响性能。但是,并不是说可实现占用率越高,性能就越好,在实验的时候要注意调整块大小。
杰伦:当我们想通过设置块的大小,网格的大小来获得最佳性能时,不能单单考虑到可实现占用率,载入吞吐量,要在指标之间寻求一个好的平衡,原则就是,块数偏多时会增加并行性,块第一维(第一维调整对载入吞吐量有绝对的影响)一定要是32的倍数。
(4)CUDA内存模型
无论是CPU还是GPU都遵循时间局部性和空间局部性,这就使得CPU和GPU内存结构设计都是低延迟低容量的层次结构,如cpu中(寄存器、缓存、主存、磁碟存储器,容量越大,延迟越高)。在CUDA的内存层次中,排序为(寄存器,共享内存,本地内存,常量内存,纹理内存,全局内存),如图所示: