控制流:
分支和分歧:
高优先级:避免同一个warp内的不同执行路径。
流控制指令(如果,切换,做,为,while)可以通过使同一个warp的线程发散来显着影响指令吞吐量; 即遵循不同的执行路径。 如果发生这种情况,不同的执行路径必须单独执行; 这会增加为这个warp执行的指令总数。
为了在控制流程取决于线程ID的情况下获得最佳性能,应该编写控制条件以尽量减少发散warps的数量。
这是可能的,因为在CUDA C编程指南的SIMT体系结构中提到了整个块上的warp分布是确定性的。 一个简单的例子是控制条件仅依赖于(threadIdx / WSIZE),其中WSIZE是warp大小。
在这种情况下,由于控制条件与warp完美对齐,所以没有warp发散。
对于只包括几条指令的分支,翘曲散度通常会导致性能损失的边际。 例如,编译器可能使用预测来避免实际的分支。 相反,所有指令都是预定的,但每个线程条件代码或谓词控制哪些线程执行指令。 带有假谓词的线程不会写结果,也不会计算地址或读操作数。
从Volta体系结构开始,独立线程调度允许warp在数据相关条件块之外保持分离。 明确的__syncwarp()可以用来保证warp已经为后续指令重新收敛。
分支预测:
低优先级:使编译器易于使用分支预测来代替循环或控制语句。
有时,编译器可能会展开循环,或者通过使用分支预测来优化if或switch语句。 在这些情况下,没有任何扭曲可以分歧。 程序员也可以使用控制循环展开:
#pragma unroll
当使用分支预测时,跳过执行取决于控制条件的指令。 相反,每个这样的指令都与根据控制条件设置为真或假的每线程条件代码或谓词相关联。 虽然这些指令中的每一条都被安排执行,但实际上只执行带有真谓词的指令。 带有假谓词的指令不写结果,它们也不评估地址或读操作数。
只有当由分支条件控制的指令数小于或等于某个阈值时,编译器才会用预测指令替换分支指令:如果编译器确定该条件可能会产生很多发散warps,则此阈值为7; 否则是4。
Loop Counters Signed vs. Unsigned:
低中优先级:使用带符号整数而不是无符号整数作为循环计数器。
在C语言标准中,无符号整数溢出语义被很好地定义,而带符号整数溢出会导致未定义的结果。 因此,编译器可以比使用无符号算术更积极地进行有符号算术优化。 在循环计数器中这一点特别值得注意:因为循环计数器的值始终为正,所以将计数器声明为未签名可能是诱人的。 但是,对于稍微好一点的性能,它们应该被声明为已签名。
例如,请考虑以下代码:
for (i = 0; i < n; i++) {
out[i] = in[offset + stride*i];
}
这里,子表达式stride *我可以溢出一个32位整数,所以如果我声明为unsigned,那么溢出语义会阻止编译器使用某些可能已应用的优化,例如强度降低。 如果相反,我被声明为有符号的,那么溢出语义未定义,编译器有更多的余地来使用这些优化。