对于CUDA,是否可以保证三元运营商可以避免分支差异?

Wes*_*ger 5 cuda

我已经阅读了很多关于CUDA分支差异的线索,告诉我使用三元运算符比if/else语句更好,因为三元运算符不会导致分支分歧.我想知道,对于以下代码:

foo = (a > b) ? (bar(a)) : (b);
Run Code Online (Sandbox Code Playgroud)

如果bar是另一个函数或更复杂的语句,那么是否仍然没有分支差异?

nju*_*ffa 6

我不知道你咨询了哪些来源,但是在CUDA工具链中,if-then-else在大多数情况下,使用三元运算符和等效序列之间没有明显的性能差异.在注意到这种差异的情况下,它们是由于代码生成中的二阶效应,并且基于if-then-else序列的代码在我的经验中可能更快.本质上,三元运算符和紧密本地化的分支以大致相同的方式处理.不能保证三元运算符不能转换为包含分支的机器代码.

GPU硬件提供了多种有助于避免分支的机制,CUDA编译器充分利用这些机制来最小化分支.一种是预测,可以应用于几乎任何指令.另一种是支持选择型指令,它本质上是三元运算符的硬件等价物.编译器使用if-conversion将短分支转换为无分支代码序列.通常,它选择了谓词代码和统一分支的组合.在非发散控制流的情况下(warp中的所有线程采用相同的分支),统一分支跳过谓词代码段.

除了极端性能优化的情况之外,CUDA可以(并且应该)使用if-then-else您认为合适的序列或三元运算符来编写清晰且适合手头任务的自然习语.编译器将负责其余的工作.


Yut*_*kit 6

(我想为@njuffa的答案添加注释,但我的名声还不够)我发现程序对它们的性能有所不同。if子句样式花费4.78ms:

// fin is {0-4}, range_limit = 5
if(fin >= range_limit){
   res_set = res_set^1;
   fx_ref = fx + (fxw*(float)DEPTH_M_H/(float)DEPTH_BLOCK_X);
   fin = 0;
} 
// then branch for next loop iteration.

// nvvp report these assemblies.
    @!P1 LOP32I.XOR R48, R48, 0x1;
    @!P1 FMUL.FTZ R39, R7, 14;
    @!P1 MOV R0, RZ;
    MOV R40, R48;
    {    @!P1 FFMA.FTZ R6, R39, c[0x2][0x0], R5;
    @!P0 BRA `(.L_35);        }    // the predicate also use for loop's branching
Run Code Online (Sandbox Code Playgroud)

三元样式花费4.46ms:

res_set = (fin < range_limit) ? res_set: (res_set ^1);
fx_ref  = (fin < range_limit) ? fx_ref : fx + (fxw*(float)DEPTH_M_H/(float)DEPTH_BLOCK_X) ;
fin     = (fin < range_limit) ? fin:0;
//comments are where nvvp mark the instructions are for the particular code line
    ISETP.GE.AND P2, PT, R34.reuse, c[0x0][0x160], PT;   //res_set
    FADD.FTZ R27, -R25, R4;
    ISETP.LT.AND P0, PT, R34, c[0x0][0x160], PT;         //fx_ref
    IADD32I R10, R10, 0x1;
    SHL R0, R9, 0x2;
    SEL R4, R4, R27, P1;
    ISETP.LT.AND P1, PT, R10, 0x5, PT;
    IADD R33, R0, R26;
    {         SEL R0, RZ, 0x1, !P2;
    STS [R33], R58;        }
    {         FADD.FTZ R3, R3, 74.75;
    STS [R33+0x8], R29;        }
    {    @!P0 FMUL.FTZ R28, R4, 14;                     //fx_ref
    STS [R33+0x10], R30;        }
    {         IADD32I R24, R24, 0x1;
    STS [R33+0x18], R31;        }
    {         LOP.XOR R9, R0, R9;                      //res_set
    STS [R33+0x20], R32;        }
    {         SEL R0, R34, RZ, P0;                     //fin
    STS [R33+0x28], R36;        }
    {    @!P0 FFMA.FTZ R2, R28, c[0x2][0x0], R3;       //fx_ref
Run Code Online (Sandbox Code Playgroud)

插入的行来自下一个循环迭代计算。我认为在许多指令共享相同谓词值的情况下,三元样式可能会为ILP优化提供更多机会。

  • @mabraham我通过使用cudaEvent来测量经过时间,该时间应该是开销较小的方法AFAIK。在进行内核测量之前,我调用了简短的虚拟内核来掩盖CUDA的API参考延迟,然后启动cudaEvent,在无内存事务的情况下循环调用内核数千次(几秒钟),重新触发cudaEvent,然后平均经过的时间。 (2认同)