title: 【CUDA 基础】3.2 理解线程束执行的本质(Part I)
categories:
- CUDA
- Freshman
tags: - 线程束分化
- CUDA分支
toc: true
date: 2018-03-14 21:31:16
Abstract: 本文介绍CUDA执行模型最核心的部分,线程束的执行实质第一部分
Keywords: CUDA分支,线程束分化
开篇废话
我们前面已经大概的介绍了CUDA执行模型的大概过程,包括线程网格,线程束,线程间的关系,以及硬件的大概结构,例如SM的大概结构,而对于硬件来说,CUDA执行的实质是线程束的执行,因为硬件根本不知道每个块谁是谁,也不知道先后顺序,硬件(SM)只知道按照机器码跑,而给他什么,先后顺序,这个就是硬件功能设计的直接体现了。
从外表来看,CUDA执行所有的线程,并行的,没有先后次序的,但实际上硬件资源是有限的,不可能同时执行百万个线程,所以从硬件角度来看,物理层面上执行的也只是线程的一部分,而每次执行的这一部分,就是我们前面提到的线程束。
线程束和线程块
线程束是SM中基本的执行单元,当一个网格被启动(网格被启动,等价于一个内核被启动,每个内核对应于自己的网格),网格中包含线程块,线程块被分配到某一个SM上以后,将分为多个线程束,每个线程束一般是32个线程(目前的GPU都是32个线程,但不保证未来还是32个)在一个线程束中,所有线程按照单指令多线程SIMT的方式执行,每一步执行相同的指令,但是处理的数据为私有的数据,下图反应的就是逻辑,实际,和硬件的图形化
线程块是个逻辑产物,因为在计算机里,内存总是一维线性存在的,所以执行起来也是一维的访问线程块中的线程,但是我们在写程序的时候却可以以二维三维的方式进行,原因是方便我们写程序,比如处理图像或者三维的数据,三维块就会变得很直接,很方便。
在块中,每个线程有唯一的编号(可能是个三维的编号),threadIdx。
网格中,每个线程块也有唯一的编号(可能是个三维的编号),blockIdx
那么每个线程就有在网格中的唯一编号。
当一个线程块中有128个线程的时候,其分配到SM上执行时,会分成4个块:
warp0: thread 0,........thread31
warp1: thread 32,........thread63
warp2: thread 64,........thread95
warp3: thread 96,........thread127
当编号使用三维编号时,x位于最内层,y位于中层,z位于最外层,想象下c语言的数组,如果把上面这句话写成c语言,假设三维数组t保存了所有的线程,那么(threadIdx.x,threadIdx.y,threadIdx.z)表示为
t[z][y][x];
计算出三维对应的线性地址是:
tid=threadIdx.x+threadIdx.y×blockDim.x+threadIdx.z×blockDim.x×blockDim.y
tid = threadIdx.x+threadIdx.y\times blockDim.x+threadIdx.z\times blockDim.x \times blockDim.y
tid=threadIdx.x+threadIdx.y×blockDim.x+threadIdx.z×blockDim.x×blockDim.y
上面的公式可以借助c语言的三维数组计算相对地址的方法,如果有人做过图像,或者矩阵,那么这个计算过程应该没啥纠结的。但是对于初学者,这个地方经常性的绕晕,就行我刚开始写图像算法的时候,经常搞不清楚长和宽。
一个线程块包含多少个线程束呢?
WarpsPerBlock=ceil(ThreadsPerBlockwarpSize)
\text{WarpsPerBlock}=\text{ceil}\begin{pmatrix}\frac{\text{ThreadsPerBlock}}{\text{warpSize}}\end{pmatrix}
WarpsPerBlock=ceil(warpSizeThreadsPerBlock)
ceil函数是向正无穷取整的函数,比如ceil(98)=2ceil(\frac{9}{8})=2ceil(89)=2
线程束和线程块,一个是硬件层面的线程集合,一个是逻辑层面的线程集合,我们编程时为了程序正确,必须从逻辑层面计算清楚,但是为了得到更快的程序,硬件层面是我们应该注意的。