SIMD,Single Instruction Multiple Data,单指令多数据。
假设上图中的PU都为加法器,那么,当从指令存储取出来一条加法指令的时候,将这条加法指令同时给到四个加法器,加法器再去数据存储取出各自的数据做加法运算。
SIMD的指令举例如下:
add.4 arr1 arr2
假设,
arr1 = {1, 2, 3, 4};
arr2 = {2, 3, 4, 5};
那么,期望的执行结果为{3, 5, 7, 9}
但如果arr1
和arr2
的取值是下面这种情况,
arr1 = {1, 1, 1, 1};
arr2 = {2, 2, 2, 2};
此时,期望的执行结果为{3, 3, 3, 3}
可以看到,这时四个加法器做的操作一模一样,都是1 + 2 = 3
. 那能不能提前检测这种情况并做好标记,在执行的时候,根据这个标记来决定启用一个加法器还是四个?
这完全是可行的!
在编译阶段就能检测到很多类似这样的指令,比如:
mov i <- 0
add i <- i + 1
上面两条指令在编译器阶段就都能知道属于上述类型的指令。当然也可以在硬件做。
原先需要四个加法器同时工作,而现在只需要一个加法器工作即可,节省了功耗。
在这基础之上,我们可以更进一步优化...
现在有三个加法器是不工作的,我们可以想办法把他们利用起来。假设指令执行情况是这样:
w0.add.4 arr1 arr2
w1.add.4 arr1 arr2
...
那么,w0.add.4
执行在第一个加法器上,与此同时,可以让w1.add.4
执行在第二个加法器上,如此便提升了处理器的执行效率。
按照上述的合并SIMD中空闲Lane的思路,我们可以进一步应用到分支处理的情形,优化存在分支指令时的执行效率。
例如,指令A的执行Mask为0b1110,即Lane0不执行,其他三个Lane都执行。下一条指令B的执行Mask为0b0001,即Lane0执行,其他三个Lane都不执行。那么可以把两条指令合并到一起。执行指令A时没有启用的Lane0用来执行指令B。这样便提升了执行效率。
如果把上述两种优化方法结合使用,执行效率便会提升更多。
以上内容参考自《Scalar Waving: Improving the Efficiency of SIMD Execution on GPUs》,该文还给出了具体实现细节,有兴趣的可以进一步阅读。