高性能CUDA应用设计与开发第四章阅读笔记
CUDA调度层次结构
千兆全局调度器 -> 流式处理器(SM) -> wrap调度器 -> SIMD处理器核心 -> 线程
以GF100流多处理器距离为例
2个wrap调度器:给SIMD核心分配任务
32个SIMD核心:单指令多线程调度,并发执行一个或半个wrap(32)的线程
16个存取单元(LD/ST)
4个特殊运算单元(SFU)
wrap指令分配
GF100有两个wrap调度器和两个指令分配单元
这使得两个wrap调度器可以并行执行
wrap调度器的大部分指令都可以双发射并行处理
如整型,浮点型,加载,存储的混合与SFU混合的指令(双精度不支持)
SFU,LD/ST以及SIMD核心都是彼此独立的,所以他们可以并行执行
wrap分歧
一个wrap内的线程各条件分支子句间是串行执行的,例如
if p:
...
else:
...
其中进入第一个分支的线程会首先执行,非这个分支的线程会挂起
之后第二个分支的线程执行,其他线程挂起
毕竟SIMD要求的是单指令多线程
所以条件分支过多,会导致运行速度成倍减少…
Fermi架构GPU的分支预测技术:
在使用分支预测时,所有分支会并行执行
但条件为假的指令不会被写回结果,计算地址或读取操作数(这些开销没了)
只有条件分支的数量小于一个阈值的时候才会使用分支预测(分支太多并行开销也会比较大)
编译器在推断条件会导致产生许多有分歧的wrap时阈值为7,否则阈值为4
wrap表决
分支中代码过长,nvcc编译器还会插入代码执行wrap表决,检查wrap内所有线程是否执行相同的分支
all(int predicate):如果当前线程所在的Wrap所有线程的predicate不为0,则返回1。 any(int predicate):如果当前线程所在的Wrap有一个线程的predicate值不为0,则返回1。
__ballot(int predicate):Wrap中第N个线程的predicate值不为0,则将整数0的第N位进行置位。
通过此方式决定是否要进行分支预测
某些情况编译器能够在编译时确定wrap内所有分支是否会执行相同路径
例如将一个共享变量作为分支变量,此时所有线程将进入同一分支没有开销
线程级并行(TLP)
书上说了很多…其实就一个意思,多添线程块提高占用率,保持各种单元(存储,执行,特殊运算)繁忙
指令级并行(ILP)
高占用率不一定是最佳应用性能
使用ILP的原因:使用较少的线程意味着每个线程可以使用更多的寄存器
为每个线程多分配一点寄存器可以防止寄存器溢出并保持高性能
pragma unroll 预编译指令
pragma unroll 16可以将for循环内的代码展开16次
这样就减少了for循环中的比较与分支操作
更多的独立指令可以提高ILP并隐藏流水线与内存访问的延迟
总而言之言而总之
所有的存储器中只有寄存器能够满足GPU数以万计的计算要求的存储带宽需求
寄存器很重要!!!
然后wrap分歧问题需要十分重视!!!
尽量以等价形式的代码减少wrap分歧!!!
