Branch Prediction和Branch Predication都是针对程序分支语句影响硬件执行效率而提出的技术。Branch Prediction应用于CPU,目标是保证最高的线程执行效率。Branch Predication应用于SPMD结构的运算设备,这类设备以吞吐量为首要目标,GPU就是这类设备的代表。
Branch Prediction
Branch Prediction,即分支预测,目的是保证大部分时间下CPU流水线都处于满负荷状态,保证正在执行的线程以最高效率运行。CPU流水线可大致划分为4个阶段:指令预取、指令解码、执行和结果回写。当程序代码出现条件分支语句时,条件分支语句的下条指令直到条件结果计算完毕后才能确定。在结果计算完毕前,CPU流水线都无法进行指令预取和指令解码(这是因为无法确定下一条指令),也就是流水线stall了。为了解决流水线stall,提升执行效率,CPU的分支预测硬件模块在条件结果计算完毕前,根据程序的历史运行记录,预判下一条指令的位置并将其应用于流水线。如果条件结果计算符合预期或者说预判正确,CPU的流水线就不会stall,执行效率最高。
如果CPU不引入分支预测,那么,条件分支语句的执行效率永远是最低(因为流水线必然stall),引入分支预测后,CPU流水线就会有一定几率不stall,提升了执行效率,特别地,当程序编写时有意迎合CPU分支预测机制,那么程序就能高效的执行,例如对一个int数组内大于100的元素进行操作,那么一个已排序的数组跟一个没有排序的数组执行相同的代码,执行效率显然是已排序数组的情况下高。下面是伪代码:
for (int i in IntArray)
{
if (i > 100)
{
// do
}
}
当这段代码应用于已排序的数组时,CPU分支预测失败的情况(也就是说流水线stall)可能只发在i满足大于100这个条件的前几次循环里,而大部分时间,CPU的流水线都是满载运行。如果数组没有排序,CPU分支预测失败的情况就会很普遍,代码的运行效率就会下降。
最后,分支预测需要硬件模块的支持,所以必然会提升硬件复杂度和功耗。
Branch Predication
首先解析SPMD及对应的硬件结构。SPMD就是Single Program Multiple Data,可以理解为SIMD的扩展。在SIMD情况下,对多数据的处理只限于单条指令,而SPMD情况下,程序编写者可以按需要编写一个处理多数据的程序,这个程序在多个线程中并行执行。GPU的Shader程序就是SPMD的一个例子。
硬件结构上,将多个ALU绑定一起,共享一个PC(Program Counter),就能组成一个SPMD运算单元,例如NV GPU的SMX,AMD GPU的CU都是SPMD运算单元。一个SPMD运算单元包含的ALU数量决定了线程的理论最大并行数量。为了简化硬件结构,降低功耗,SPMD运算单元并没有为每个ALU配备分支预测硬件模块,更重要的是,在共享PC前提下,为每个ALU配备分支预测硬件模块对执行效率根本没有任何提升。
条件分支的出现将导致SPMD运算单元内ALU的执行路径不一致。由于这些ALU共享同一个PC,所以,要求ALU并行执行不同路径是不可能的。要在SPMD上实现条件分支,就需要编译器对条件分支程序代码进行等价变换,把不一致的执行路径转换成一致的执行路径,看下面这个例子:
A.原始代码,执行路径不一致
int condition = threadid + 100;
int result = 0;
if (condition > 220)
{
result += 10;
}
else
{
result -= 10
}
B.一种可能的转换后的代码
int result = 0;
int condition = threadid + 100;
int temp1 = result + 10;
int temp2 = result - 10;
result = (condition > 220) ? temp1 : temp2;
转换后的代码在每个ALU上的执行路径都一致,不存在条件分支。两个分支都执行,根据条件选择接受哪个分支的结果,就叫Branch Predication分支断定。
Branch Predication需要硬件指令集的支持,支持Branch Predication的指令根据某些状态寄存器的值,执行不一样的逻辑,例如x86的cmov系列指令,又例如某些厂商GPU指令会根据状态寄存器的值选择执行还是NOP(传说中的指令级多态?)。
使用Branch Predication转换后的代码,执行时间比条件分支代码长,因为转换后的代码,所有分支都会执行。于是,有人提出了一种类似动态指令融合的技术,在SPMD里既可保留条件分支,也保证执行效率——Dynamic warp formation。
Dynamic warp formation
Warp就是线程组threadgroup的意思,是SPMD运算单元的最小并行粒度,最小调度单位(NV称为warp,AMD称为wavefront)。例如,NV的warp为32线程,就是说,在SPMD运算单元内,每32个ALU并行执行相同的指令。也可以把warp看作是一条硬件线程,它的执行宽度为32个ALU。相对于CPU的线程,其执行宽度通常为1个ALU。当然了,GPU一个ALU的宽度可达4*64 bit,那么,GPU一个warp的宽度就是32*4*64 bit,并且,GPU在同一时间可执行多个warp,所以单比吞吐量,GPU与CPU不在一个数量级。
以下均使用warp表示线程组,并以32线程为一个warp为前提展开。回顾SPMD出现条件分支的情况,例如出现分支A及分支B,那么,warp内32个线程既执行分支A也执行分支B,最后,这些线程根据分支条件的结果选择接受A的结果还是接受B的结果。无论如何,总有一部分结果对于这些线程来说是没有意义的。
把一个warp按分支路径拆分成多个子warp,并行执行,也不会带来任何效率的提升,因为拆分的子warp宽度肯定不够32。如果把这种思路应用于多个warp的话,情况会有所不同,例如warp1,warp2和warp3执行相同的代码并到达相同条件分支语句,分支将产生路径A和路径B,按路径拆分出的子warp重组成新warp并行执行,分支路径执行完毕后还原为原来的warp,这样就能高效应对条件分支,不会出现没有意义的运算,这就是Dynamic warp formation,更详细的介绍可自行google。