简介
线程块中线程总数的大小除了受到硬件中Max Threads Per block
的限制,同时还要受到Streaming Multiprocessor、Register
和Shared Memory
的影响。这些条件的共同作用下可以获得一个相对更合适的block
尺寸。当block
尺寸太小时,将无法充分利用所有线程;当block
尺寸太大时,如果线程需要的资源总和过多,CUDA
将通过强制减少block
数量来保证资源供应,同样无法利用所有线程。而grid
的尺寸通常越大越好,当grid
中的线程总数超过一次所能启动的并发线程总数时,过多的线程将以线程块为单位由CUDA进行新的调用,当然启动数量够用就可以了,以免浪费资源。
但具体最多我们可以定义的grid尺寸是多大跟计算能力倒是关系不大,所以你会发现消费级GTX970与专业级Tesla P4的计算能力分别是5.2和6.1,但可以定义的grid尺寸却相同。在我的测试中,同一段代码,一维网格和block的情况下,Block尺寸都为1024时,GTX1080的grid在win10系统桌面由显卡驱动的情况下,最多可以是78(WIN10系统。而linux下的Tesla P4和GTX970的grid最大值都是680。
然而二维grid和block的时候,block尺寸虽然有1024的限制,但grid尺寸可以大到你的int放不下,当然我们并没有这么多的流处理器在运算,只是过多的block在排着队等着被执行。
而CUDA的调度中为了隐藏内存访问带来的latency,一个block中的线程数量越多越好(确切来说是wrap数量)。
本文需要通过NVIDIA
官方提供的一个非常有用的工具C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v7.0\tools\CUDA_Occupancy_Calculator.xls
进行辅助计算来获得合适的block
大小。这个excle
表中的Help
表单有详细的使用说明,以下分析将基于这个工具。
可以通过在编译时添加--ptxas-options=-v
参数在编译时输出当前程序中核心函数所使用的Register
、Shared Memory
等大小,或者使用nvcc --resource-usage kernel.cu
来获取,注意此处获取到的值只是nvcc在编译期为核函数分配的相应空间,如果通过动态的方式分配过shared memory,则需要单独加上你手动动态分配的shared memory才是总是shared memory。
输出的ptxas info可能包含的信息表示如下:registers
: 寄存器lmem
: local memory
局部内存smem
: shared memory
共享内存cmem
: constant memory
常量内存
其中cmem
又可以分以一下一些情况(不全):cmem[0]
kernel argumentscmem[2]
user defined constant objectscmem[16]
compiler generated constants (some of which may correspond to literal constants in the source code)
参考:https://devtalk.nvidia.com/default/topic/493425/ptxas-info-unexplained-what-is-cmem-n-/
显卡信息可以由官方示例中的deviceQuery
得出,当然也可以查官方说明。
看着这个表挺复杂,其实只需要记住我们所要设定的线程块尺寸只要能够保证SM满占即可。
SM的限制对block尺寸的影响
Multiprocessor
(以下所有的Multiprocessor
都是指Streaming Multiprocessor
,即SM,具体SM在GPU中的结构组织参见http://notes.maxwi.com/2015/06/11/CUDA-study-notes/基本概念中的图示)中thread
数量的限制主要影响工作状态的线程是否能够占满当前的整个Multiprocessor
。为了使工作状态的线程能够占满整个Multiprocessor
,block
中的thread
的总数应当不小于Max Threads Per Multiprocessor / Max Thread Blocks Per Multiprocessor
,对于该表,也就是使Occupancy of each Multiprocessor
为100%
即可。Max Thread Blocks Per Multiprocessor
无法由deviceQuery
得到,表中会给出,其实也很容易计算得到Max Threads Per Multiprocessor / 32
(因为一个block一次最小也要启动一个wrap,即32个线程),同一计算能力的显卡这些基本信息都是一样的。
下图所示为CUDA_Occupancy_Calculator给出的计算能力为5.0的GPU的参数。
此时线程块的中线程数量应当不小于2048/32 = 64。当线程块的数量小于64时,由于每个Multiprocessor中可以含有的最大线程块只有32个,所以此时32个线程块乘以这个小于64的数字必然要小于2048,也就是无论怎样都无法使线程填满整个Multiprocessor,导致SM中会有空闲的Streaming Processor。所以根据要填满SM,则要求每个线程块中至少需要有64个线程。如果将线程块尺寸设置为1024,则此时根据SM总线程的限制2048,每个SM正好可以启动2个线程块,如果我们每个线程块上的资源都够用,那这就是一个最佳尺寸。然而通常资源是最大限制,所以下面借助官方工具来综合考虑资源占用下的线程块尺寸。
注意:1). 当前计算能力5.0的设备每个线程块中线程数量上限Maximum Thread Block Size=1024、2). CUDA中线程组织单位为Wrap,此处Threads per Warp=32,所以线程块中的线程数量应为32的倍数。3). 各维都有大小限制,计算能力5.0的三维分别为1024,1024,64
CUDA_Occupancy_Calculator的使用
下图为CUDA_Occupancy_Calculator
计算结果,由于此处只讨论了SM中线程及线程块的限制对block
尺寸的影响,所以这里只需要使曲线的红三角在波峰即可。注意第一张图中的1.)、1.b)需要根据自己的硬件情况进行选择;2).需要自己填入的程序参数(可以通过2.2的方法获得),3).工具计算出的GPU使用率,显示此时占用为100%,也就是SM中的活动线程束为64个,即64*32=2048,SM处于满占状态。其他各图是其对应的曲线,由第二幅图可以看出在register
和shared memory
固定的情况下,block尺寸设置为256, 512, 1024时都可以占满SM,block尺寸越小,同一个block中可使用的Shared Memory和每个线程中可使用Register就越大,因为同一个SM中的block共享这些资源。。它们都有以下特点:1). 大于64,满足不小于Max Threads Per Multiprocessor / Max Thread Blocks Per Multiprocessor
; 2). 32的整数倍,满足线程束的最小单位Wrap; 3). 总线程数可以被2048整除,因为Max Threads Per Multiprocessor=2048
。
Register对block尺寸的影响
计算能力为5.0的设备Registers per Multiprocessor=65536,Max Registers per Thread=256
。也就是说一个SM总共也就只有65536个register
,一个thread
最多能定义256大小的register
,根据上图可以看出,显然当Register Per Thread
大于32时性能就要开始降低了。因为当前情况下SM中的所有线程都被占满了(此处占满意思是所有线程都为活动线程),也就是说在这种block参数配置下,一个SM最多可以启动2048个线程(注意是最多可以,并不是说一定要,比如我一共就1000个数据,当然就启动1000个线程就可以了),由于SM中能使用的register最多只有65536,当SM中的资源不够用时,SM就会强制减少block,所以Register Per Thread
应该不大于Registers per Multiprocessor / Max Threads per Multiprocessor
,也就是65536 / 2048 = 32,如何降低register占用是个很难的调整阶段,只能慢慢多调
Shared Memory对block尺寸的影响
计算能力为5.0的设备Shared Memory per Multiprocessor (bytes)=65536,Max Shared Memory per Block=49152
。也就是说block中的smem(Shared Memory)
必须要小于49152,要想使得SM中的线程全部占满,那么整个SM中占用的smem必须小于65536。由于smem是以block为单位进行分配,所以当smem不够用时也就会以block为单位进行减少线程。所以Shared Memory Per Block (bytes)
应该不大于Shared Memory per Multiprocessor / Active Thread Blocks per Multiprocessor = Shared Memory per Multiprocessor / (Max Threads per Multiprocessor / Threads Per Block)
,当block尺寸设置为256时,Shared Memory Per Block (bytes)
最大值为65536 / (2048/256) = 8192,单位是bytes。
总结
block最佳尺寸应该满足:
1). 不小于Max Threads Per Multiprocessor / Max Thread Blocks Per Multiprocessor
;
2). 32的整数倍;
3). 可以被Max Threads Per Multiprocessor
整除。
4). Register Per Thread
应该不大于Registers per Multiprocessor / Max Threads per Multiprocessor
,否则根据CUDA_Occupancy_Calculator.xls
参考调节block尺寸以获得最佳性能。
5). Shared Memory Per Block (bytes)
最大值应该不大于Shared Memory per Multiprocessor / (Max Threads per Multiprocessor / Threads Per Block)
,否则根据CUDA_Occupancy_Calculator.xls
参考调节block尺寸以获得最佳性能。
一句话总结:保证每个SM中可以启动的线程总数达到最大值的情况下block中的线程数越大越好。
如果原理都懂了,也可以直接使用CUDA 6.5之后带的一个API,自动帮你计算Grid尺寸和block尺寸:cudaOccupancyMaxPotentialBlockSize()
,该API在cuda_runtime.h
头文件中,必须采用NVCC编译才能使用,其定义如下:
1 | template<class T> |
参数意义:
1 | minGridSize = Suggested min grid size to achieve a full machine launch. |
注意:
- 没有标明单位的量其单位都为个,例如register。
CUDA_Occupancy_Calculator
给出了register
和shared memory
的分配单位,精确优化时应该非常有用。
Register allocation unit size 256
Register allocation granularity warp
Shared Memory allocation unit size 256
Warp allocation granularity 4