调度可针对吞吐率最大化,响应时间最小化,最低延迟或最大化公平进行设计。
线程块被分配给一个可编程多处理器后,GPGPU会根据线程编号,将若干相邻线程组成线程束,按照锁步方式执行,所有线程进度一致,一个线程束共用一个PC,多个线程等价于多个向量操作,其向量宽度也就是线程束大小。
可编程多处理器执行时可达到的线程并行度是由线程块、线程、寄存器和共享存储器中允许的最小并行度决定的。
不同线程束的不同指令可能会细粒度地交织在一起,而同一个线程束的指令是顺序执行的。
就绪的指令满足以下三个条件:下一条指令已经取到,指令的所有相关性都已经解决,指令需要的执行单元可用。
指令缓冲区采用一个简单的表结构,表象数目与可编程多处理器所允许的最大线程ID数量相关。每个表项包括:线程块ID,线程束ID和线程ID。
图1 线程束调度器条目的基本结构
早期的线程束调度器采用轮询(Round-Robin,RR)调度策略,与此对应的另一种策略为GTO(Greedy-Then-Oldest)如图2。
对比两种策略,轮询调度器对于处于就绪状态的线程束0、1、3、4、5都赋予相同的优先级,并按照轮询的策略依次选择处于就绪状态的线程束指令进行调度,完成后再切换到下一个就绪线程束;GTO调度器首先选择了线程束0的前3条指令执行,直到无法执行指令3,此时再切换至线程束1的前3条指令执行。
图2 基本的轮询调度和GTO调度
数据访存延时是影响性能的主要因素,而发掘数据的局部性则是改善的最有效的手段之一。
由于SIMT架构的特点,内核函数往往存在两种数据局部性:线程束内局部性(再次访问的是同线程束的线程)和线程束间局部性(再次访问是其他线程束的线程)。再次访问可能是数据本身也可能是相邻地址的数据,因此包含了时间和空间两种局部性而言的。
由图3使用轮询策略对16个线程束W0,W1,...,W15进行调度,每个线程束中I0~Ik-1均为运算指令,Ik为访存指令。可以看出即使相邻线程束执行Ik只差一个周期,线程束W0的访存操作几乎不可能在16个周期完成,而此时也没可以调度的线程束来隐藏访存延时。而GTO策略可以由别的线程束用于掩藏延时,但可能会破坏线程间局部性,在高速缓存很小的情况下可能会导致缓存数据重用不足甚至抖动现象,反而拉长平均访存延时。
图3 基本轮询调度存在的问题
1.利用并行性掩藏长延时操作
(1)两级轮询调度
将所有线程束分成2组,第0组有较高优先级,第1组优先级次之。组内按照轮询策略依次执行,等第0组组内8个线程都执行到访存指令Ik时,将第0组优先级置为最低,给第一组最高优先级并调度执行,因此可以很好的隐藏第0组访存操作带来的长延时。
图4 两级轮询调度改善了长延时操作掩藏的能力
(2)线程块感知的两级轮询调度
两级:第一级为线程块级,下一级为线程束级,在两级的调度仍选取RR策略。组一级线程块之间按照RR进行调度,当组内所有线程束都因长延时而被阻塞时,调度切换至下一组线程块继续执行,线程块内线程束优先级相同,同样采用RR。
(3)结合数据预取的两级调度策略
图 5 轮询调度和预取
2.利用局部性提高片上数据复用
当数据被加载到片上存储尤其是L1数据缓存后,如果能有效地重用这些数据提高缓存的命中率,既可以减少访存的延时,又可以减少重复的访存操作,即减少长延时访存操作次数。
缓存感知的调度策略(CCWS)是一种带有反馈机制的,可动态调整的线程束调度方案,核心思想:如果线程束发生缓存局部性缺失,则为它提供更多的缓存资源,以降低可能复用的数据被替换出缓存的可能性。
3.线程束进度分化与调度平衡
(1)多调度器协同策略
图6 多线程束调度器协同对程序执行性能的影响
(2)线程束动态均衡调度策略
一个调度器下出现线程执行进度分化的原因:不单一线程块由于存储系统访问的不确定性,线程束的执行进度存在较大差异;线程同步栅栏处或在分支重聚处执行快的线程需要等待执行慢的线程。因此需要缩短最快和最慢之间的执行差距。
关键性感知的线程束协调加速(CAWA):执行时间最长的线程束为关键线程束,给予关键线程束最高调度优先级,分配更多硬件和时间资源以满足执行需求。
影响线程束关键性的因素:(1)线程分支导致的工作负载差异,哪个路径指令多,对关键性影响越大;(2)共享资源竞争引入的停顿。
基于GTO的关键性感知线程束调度策略(gCAWS),改进了调度选取线程束的机制,每次选择关键度最高的一个线程束执行,当有多个关键度相同的线程束时,选择生命周期最长的线程束执行,在执行阶段不断更新关键度的值。
(1)写后读(RAW),又名真数据相关。
(2)写后写(WAW),又称名称相关。
(3)读后写(WAR),又称反相关。
其中WAW和WAR除了可以保持指令顺序保证正确性,还可以通过寄存器重命名技术来消除相关性。
在GPGPU中,为了提升SIMT运算单元的硬件效率,会顺序执行,但其指令的执行仍然可能需要多个周期才能完成,且不同指令存在不等长执行周期情况,为了让同一线程束的后续指令在发射时减少等待时间,也需要保证不存在数据相关.主要避免发生RAW和WAW冒险,WAR一般不太可能发生。
记分牌方案:每个线程束分配1bit记录对应寄存器的写完成状态。若正在执行的线程束指令将要写回寄存器Rx,则将Rx置为1,表明尚未写完,该Rx处于非就绪状态,不能被调度或发射。完成写回后再将Rx置为0。如果同一线程束后续指令不存在数据相关,则可尽早进入流水线执行。
存在的问题:
(1)若每个寄存器都分配1bit标识,记分牌将占用大量空间;
(2)所有待发射线程束指令在调度时需要一直查询记分牌,直到所依赖的指令执行完毕,更新寄存器标识位,才能发射后续指令,将带来巨大开销。
1. 基于寄存器编号索引的记分牌设计
将记分牌的存储空间划分为若干区域如图8(a),由于只有指令缓冲已解码的指令才有可能发射,因此将记分牌存储空间划分为与指令缓冲中指令数目相同的区域。
每个区域包含若干条目,每个条目包含两个属性:寄存器RID和尺寸指示器。寄存器RID记录该区域所对应线程束目前正在执行的若干指令中,将要写回的目的寄存器编号。若该目的寄存器为一个序列,则尺寸指示器则负责记录该寄存器序列长度,RID只需要记录序列中第一个寄存器RID(减少记分牌存储空间的使用量)。其检查过程如图8(b)。
图8 一种基于寄存器编号索引的记分牌硬件设计及相关性检查过程
2. 基于读写屏障的软件记分牌设计
软件记分牌设计:首先设计一定量的读写屏障,借助编译器分析,显式地将存在相关性的寄存器绑定到某个读写屏障上,在运行时,目的寄存器的写操作可以直接设定绑定的读写屏障,而源寄存器的读操作需要读取绑定的读写屏障来获知寄存器的写操作是否写完。这些信息由编译器提供,可以节省硬件开销,降低搜索代价,从而快速定位到绑定的读写屏障。
基本的线程块调度策略也是轮询,与线程束调度类似。
1. 感知空间局部性的调度策略
1) 感知L1缓存局部性的块级线程块调度
2)感知DRAM板块的线程块协同调度
2.感知时间局部性的抢占调度策略
3.限制线程块数量的怠惰分配和调度策略
4.利用线程块重聚类感知局部性的软件调度策略