#CUDA: Occupancy(占用率)详解
占用率是指每个多处理器(Streaming Multiprocessor,SM)的活动线程束(warps)数量与实际的活动warps数量的比率。
高的占用率不一定能提升性能,但低的占用率会降低内存延迟隐藏的作用,
Higher occupancy does not always equate to higher performance-there is a point above which additional occupancy does not improve performance. However, low occupancy always interferes with the ability to hide memory latency, resulting in performance degradation. 1
通常认为:一个SM中所有的线程在同时工作,那么这个SM的效率或占用率就是100%,在这种情况下,处理程序的速度是最快的,效率是最高的。如果实际中由于资源的限制(如,共享内存,寄存器),不是所有的线程都同时工作,这时,称正在运行的线程为活跃(actived)或常驻(resident)线程。
占用率一般受三个条件的限制:
1.SM最大并发线程数,SM最大并发Warp数,SM最大并发块数
2.共享内存资源限制
3.寄存器资源限制
以上三个资源跟硬件的计算能力相关,见 1 Table14
每个SM是GPU处理数据的最基本单元,它们运行都是独立并行的,SM的数量通常最直观地体现了GPU硬件的能力。
##SM最大并发线程数,SM最大并发Warp数,SM最大并发块数
SM最大并发线程数:即一个SM中最多同时执行的线程个数。
SM最大并发Warp数:即每个SM中最多执行的Warp数。
SM最大并发块数:即每个SM中最多执行的并发数。
这些参数决定了SM可最大并发线程数的理论值以及在不考虑资源(寄存器,共享内存)的情况下怎么分配块以达到最大的理论值。
由于每个Warp必然包括32个线程,我们假设每个块的线程数是32的倍数(一般都这样做),则线程数 = warp数×32。所以前两个指标是完全相关的。可以看作一问题或参数考虑。
SM最大并发的块数,块本身是个逻辑上的概念,也是编程中可控制的参数,反映到实际或物理上,其实是Warp每块。块的大小影响会直接影响占用率,有时会通过共享内存间接影响占用率。
举个例子:
GTX1050Ti中
SM最大并发线程数,SM最大并发Warp数,SM最大并发块数分别为:2048,64,32。
如上所说,让2048个线程同时运行即完成了达到了最优的占用率。为实现这一目标,假设在资源(寄存器和共享内存)允许的情况下:可以有很多中实现的情况,如:
2个1024大小的块
4个512大小的块
8个256大小的块
…
64个32大小的块
即SM最大的并发的线程数是2048,在不考虑资源的情况,其分配有6种情况。
##寄存器资源的限制
寄存器资源属于片上资源,具有很低的延迟,它作为一种稀有资源有时会成为占用率提高的瓶颈,所有的SM中的线程共享一定数量的寄存器资源,寄存器一次分配给一整个块,所以,如果一个块使用了一定数量的寄存器,SM上的常驻的寄存器数量就会减少。当一个块使用的寄存器数量超过了一定阈值(这根阈值由硬件决定,下面会讲到),寄存器不足以被所有块平均分配,此时,每个SM所能活跃或常驻的块就不能达到理论值或理想值,理论的占用率就不能达到100%。
One of several factors that determine occupancy is register availability. Register storage enables threads to keep local variables nearby for low-latency access. However, the set of registers (known as the register file) is a limited commodity that all threads resident on a multiprocessor must share. Registers are allocated to an entire block all at once. So, if each thread block uses many registers, the number of thread blocks that can be resident on a multiprocessor is reduced, thereby lowering the occupancy of the multiprocessor.
那么寄存器的阈值有那些呢?
- Number of 32-bit registers per multiprocessor
64 K - Maximum number of 32-bit registers per thread block
32 K - Maximum number of 32-bit registers per thread
255
即:
1.每个SM含有的寄存器数:
2.每个块最多含有的寄存器数
3.每个线程最多含有的寄存器数
首先,每个SM含有的寄存器数64KB,如果SM中每个线程都满载,每个线程可以分到32个32位寄存器,占用率是100%。每个块最大数量的寄存器是32K,但在这种情况下,只有块的大小是1024,也就是每个线程是32个寄存器的情况,SM中的线程才能被占满,占用率才能达到100%。每个线程最大的寄存器数255个,是一个线程所能有的寄存器的最大值。
每个块最多含有的寄存器数,每个线程最多含有的寄存器数都是块和线程所能拥有的寄存器的极限值,但这并不是最优值。达到极限值不能使占用率达到100%。而每个SM含有的寄存器数,决定了达到最优值的每个线程寄存器的上限(32个)。
总之,在不考虑共享内存,且块分配合理(见上6中分配方式)的情况下,只要核函数寄存器的数目小于32,理论上占用率就可以达到100%。
不能不提的是,有时占用率高并不意味这程序就快,有时低的占用率,使用更多的寄存器是更优的选择。(全局内存(或L2缓存)访问延迟非常之高)
##共享内存资源限制
首先明确的是,共享内存是块内的资源,其基本考虑单元是块。
每个块内线程要根据自己的索引操作共享内存,但注意的是使用__syncthreads()进行同步。
那么共享内存的阈值有那些呢?
- Maximum amount of shared memory per multiprocessor
96 KB - Maximum amount of shared memory per thread block
48 KB
每个SM最大的共享内存数:96KB
每个块最大的共享内存数:48KB
每个SM最大的共享内存数:96KB,意味这如果2048个线程同时工作,每个线程可以分到48个字节,
的共享内存。每个块最大的共享内存数:48KB,意味这一个块内所限制的共享内存数为48KB。
总之,在不考虑寄存器,且块分配合理(见上6中分配方式)的情况下,如果每个线程使用的共享内存多于48个字节,则占用率会下降。
不能不提的是,有时占用率高并不意味这程序就快,有时低的占用率,使用更多的共享内存是更优的选择。(全局内存(或L2缓存)访问延迟非常之高)
##占用率的作用
GPUs thrive on parallelism and do so by covering memory latency with work from other threads2.
或见CUDA:延迟隐藏详解
修改(补充):
单个核函数内的计算耗时,可以隐藏其它内核内读取内存的时间,因此如果占用率越高,越有机会隐藏延迟