为什么是;a=(b>;0)?1:0;优于;如有其他",;CUDA版本?

为什么是;a=(b>;0)?1:0;优于;如有其他",;CUDA版本?,cuda,Cuda,你能告诉我为什么吗 a =(b>0)?1:0 比 if (b>0)a=1; else a =0; CUDA版本?请详细说明。非常感谢 Yik < P>不知道CUDA,但在C++和C99中,使用前者可以初始化const变量。< /P> int const a = (b>0) ? 1 : 0; 而对于后者,您不能使成为变量常量,因为您必须在if之前声明它 请注意,它可以写得更短: int const a = (b>0); 你甚至可以去掉括号。。。但我认为这并不能提高

你能告诉我为什么吗

a =(b>0)?1:0

if (b>0)a=1; else a =0;
CUDA版本?请详细说明。非常感谢


Yik

< P>不知道CUDA,但在C++和C99中,使用前者可以初始化const变量。< /P>
int const a = (b>0) ? 1 : 0;
而对于后者,您不能使
成为
变量常量,因为您必须在
if
之前声明它

请注意,它可以写得更短:

int const a = (b>0);

你甚至可以去掉括号。。。但我认为这并不能提高阅读能力。

我发现它更容易阅读。很明显,整个语句的目的是设置
a
的值

其目的是将
a
赋值给两个值中的一个,而三元条件运算符语法使语句中只有一个
a=


我认为标准的if/else都在一行上是丑陋的(不管它是用来做什么的)。

一般来说,你需要避免CUDA代码中的分支,否则你可能会得到扭曲发散,这可能会导致巨大的性能损失
if
/
else
子句通常会根据表达式的测试产生分支。消除分支的一种方法是使用一个表达式,如果编译器足够聪明,则可以在没有分支的情况下实现。这样,英伟达编译器中的所有线程都遵循相同的代码路径。这是一个小测试的结果,以确定是否仍然如此:

__global__ void branchTest0(float *a, float *b, float *d)
{
        unsigned int tidx = threadIdx.x + blockDim.x*blockIdx.x;
        float aval = a[tidx], bval = b[tidx];
        float z0 = (aval > bval) ? aval : bval;

        d[tidx] = z0;
}

__global__ void branchTest1(float *a, float *b, float *d)
{
        unsigned int tidx = threadIdx.x + blockDim.x*blockIdx.x;
        float aval = a[tidx], bval = b[tidx];
        float z0;

        if (aval > bval) {
            z0 = aval;
        } else {
            z0 = bval;
        }
        d[tidx] = z0;
}
使用CUDA 4.0发行版编译器编译compute capability 2.0的这两个内核时,比较部分会产生以下结果:

branchTest0:
max.f32         %f3, %f1, %f2;

三值运算符被编译成一条浮点最大值指令,而if/then/else被编译成两条指令,一条是比较,另一条是选择。这两个代码都是有条件执行的,都不会产生分支。汇编程序为这些代码发出的机器代码也不同,并且紧密复制了PTX:

branchTest0:
    /*0070*/     /*0x00201c00081e0000*/     FMNMX R0, R2, R0, !pt;


所以看起来,至少对于具有CUDA 4.0的费米GPU来说,三元运算符产生的指令比等价的if/then/else少。它们之间是否存在性能差异取决于我没有的微基准标记数据

在这两种情况下,编译器将尝试做相同的事情,它将使用谓词执行。您可以在CUDA C编程指南(可通过)和上找到更多信息。本质上,对于这样的短分支,硬件能够为分支的两侧发出指令,并使用谓词指示哪些线程应该实际执行指令


换句话说,性能差异最小。对于较旧的编译器,三级运算符有时会有所帮助,但现在它们是等效的。

一般来说,我建议以自然风格编写CUDA代码,并让编译器担心局部分支。除了谓词,GPU硬件还实现“选择”类型指令。使用Talonmes的框架并坚持使用原始海报的代码,我发现使用CUDA 4.0编译器为sm_20生成的两个版本的机器代码是相同的。我使用-keep来保留中间文件,并使用cuobjdump实用程序来生成反汇编。三值运算符和if语句都被转换为FCMP指令,这是一条“select”指令

Talonmes审查的样本案例实际上是一个特殊案例。编译器识别一些常见的源代码习惯用法,例如经常用于表示max()和min()操作的特定三元表达式,并相应地生成代码。等价的if语句不被认为是惯用语

__global__ void branchTest0(float *bp, float *d) 
{         
    unsigned int tidx = threadIdx.x + blockDim.x*blockIdx.x;
    float b = bp[tidx];
    float a = (b>0)?1:0;
    d[tidx] = a;
} 

__global__ void branchTest1(float *bp, float *d)
{
    unsigned int tidx = threadIdx.x + blockDim.x*blockIdx.x;
    float b = bp[tidx];
    float a;
    if (b>0)a=1; else a =0;
    d[tidx] = a;
}

code for sm_20
        Function : _Z11branchTest1PfS_
/*0000*/     /*0x00005de428004404*/     MOV R1, c [0x1] [0x100];
/*0008*/     /*0x84009c042c000000*/     S2R R2, SR_Tid_X;
/*0010*/     /*0x94001c042c000000*/     S2R R0, SR_CTAid_X;
/*0018*/     /*0x10019de218000000*/     MOV32I R6, 0x4;
/*0020*/     /*0x20009ca320044000*/     IMAD R2, R0, c [0x0] [0x8], R2;
/*0028*/     /*0x1020dc435000c000*/     IMUL.U32.U32.HI R3, R2, 0x4;
/*0030*/     /*0x80211c03200d8000*/     IMAD.U32.U32 R4.CC, R2, R6, c [0x0] [0x20];
/*0038*/     /*0x90315c4348004000*/     IADD.X R5, R3, c [0x0] [0x24];
/*0040*/     /*0xa0209c03200d8000*/     IMAD.U32.U32 R2.CC, R2, R6, c [0x0] [0x28];
/*0048*/     /*0x00401c8584000000*/     LD.E R0, [R4];
/*0050*/     /*0xb030dc4348004000*/     IADD.X R3, R3, c [0x0] [0x2c];
/*0058*/     /*0x03f01c003d80cfe0*/     FCMP.LEU R0, RZ, 0x3f800, R0;
/*0060*/     /*0x00201c8594000000*/     ST.E [R2], R0;
/*0068*/     /*0x00001de780000000*/     EXIT;
        ....................................


        Function : _Z11branchTest0PfS_
/*0000*/     /*0x00005de428004404*/     MOV R1, c [0x1] [0x100];
/*0008*/     /*0x84009c042c000000*/     S2R R2, SR_Tid_X;
/*0010*/     /*0x94001c042c000000*/     S2R R0, SR_CTAid_X;
/*0018*/     /*0x10019de218000000*/     MOV32I R6, 0x4;
/*0020*/     /*0x20009ca320044000*/     IMAD R2, R0, c [0x0] [0x8], R2;
/*0028*/     /*0x1020dc435000c000*/     IMUL.U32.U32.HI R3, R2, 0x4;
/*0030*/     /*0x80211c03200d8000*/     IMAD.U32.U32 R4.CC, R2, R6, c [0x0] [0x20];
/*0038*/     /*0x90315c4348004000*/     IADD.X R5, R3, c [0x0] [0x24];
/*0040*/     /*0xa0209c03200d8000*/     IMAD.U32.U32 R2.CC, R2, R6, c [0x0] [0x28];
/*0048*/     /*0x00401c8584000000*/     LD.E R0, [R4];
/*0050*/     /*0xb030dc4348004000*/     IADD.X R3, R3, c [0x0] [0x2c];
/*0058*/     /*0x03f01c003d80cfe0*/     FCMP.LEU R0, RZ, 0x3f800, R0;
/*0060*/     /*0x00201c8594000000*/     ST.E [R2], R0;
/*0068*/     /*0x00001de780000000*/     EXIT;
        ....................................

我收到一位专家的来信,他告诉我“a=(b>0)?1:0;“可以提高CUDA的性能。我很困惑。这与常量无关-这是关于消除SIMT代码中的分支。谢谢。这对于改善外观是有意义的。有人告诉我,它还提高了性能。这不是关于代码的清晰性,而是关于SIMT体系结构(如CUDA)中的性能,在CUDA中,条件块上会出现扭曲差异。没有理由不能获得性能和代码的清晰性(至少在这种情况下)。这种语法对任何有C、Java、JavaScript或C#背景的人来说都很熟悉。@nnnnnn这可能是真的,但这个问题是关于代码效率的,而这并没有回答这个问题。@davidyong-在我发布这个答案时,这个问题没有提到效率,只是问哪个版本“更好”. 两年后,关于效率的部分被编辑成了问题,编辑来自另一个账户。现在它已经引起了我的注意,我正在考虑回滚编辑-在编辑之后使现有答案出错的方式是不合适的。“如果编译器足够聪明”,这是主要的观点。无论如何,编译器应该能够在不进行分支操作的情况下将其编译为。编译器必须知道如何避免分支,并进行相应的优化。您不需要避免所有分支,这只会使代码比需要的复杂得多。您应该对代码有很好的理解,并采取措施尽量减少分歧。统一分支(所有线程采用相同的路由)不会对性能造成影响。短分支可以通过谓词实现,性能影响最小。长发散分支是唯一会招致惩罚的情况——将公共子表达式从分支中提升会有所帮助,因为程序员比编译器更了解如何重构表达式。我同意Tom的观点。“你不需要避开所有的分支
branchTest1:
    /*0070*/     /*0x0021dc00220e0000*/     FSETP.GT.AND P0, pt, R2, R0, pt;
    /*0078*/     /*0x00201c0420000000*/     SEL R0, R2, R0, P0;
__global__ void branchTest0(float *bp, float *d) 
{         
    unsigned int tidx = threadIdx.x + blockDim.x*blockIdx.x;
    float b = bp[tidx];
    float a = (b>0)?1:0;
    d[tidx] = a;
} 

__global__ void branchTest1(float *bp, float *d)
{
    unsigned int tidx = threadIdx.x + blockDim.x*blockIdx.x;
    float b = bp[tidx];
    float a;
    if (b>0)a=1; else a =0;
    d[tidx] = a;
}

code for sm_20
        Function : _Z11branchTest1PfS_
/*0000*/     /*0x00005de428004404*/     MOV R1, c [0x1] [0x100];
/*0008*/     /*0x84009c042c000000*/     S2R R2, SR_Tid_X;
/*0010*/     /*0x94001c042c000000*/     S2R R0, SR_CTAid_X;
/*0018*/     /*0x10019de218000000*/     MOV32I R6, 0x4;
/*0020*/     /*0x20009ca320044000*/     IMAD R2, R0, c [0x0] [0x8], R2;
/*0028*/     /*0x1020dc435000c000*/     IMUL.U32.U32.HI R3, R2, 0x4;
/*0030*/     /*0x80211c03200d8000*/     IMAD.U32.U32 R4.CC, R2, R6, c [0x0] [0x20];
/*0038*/     /*0x90315c4348004000*/     IADD.X R5, R3, c [0x0] [0x24];
/*0040*/     /*0xa0209c03200d8000*/     IMAD.U32.U32 R2.CC, R2, R6, c [0x0] [0x28];
/*0048*/     /*0x00401c8584000000*/     LD.E R0, [R4];
/*0050*/     /*0xb030dc4348004000*/     IADD.X R3, R3, c [0x0] [0x2c];
/*0058*/     /*0x03f01c003d80cfe0*/     FCMP.LEU R0, RZ, 0x3f800, R0;
/*0060*/     /*0x00201c8594000000*/     ST.E [R2], R0;
/*0068*/     /*0x00001de780000000*/     EXIT;
        ....................................


        Function : _Z11branchTest0PfS_
/*0000*/     /*0x00005de428004404*/     MOV R1, c [0x1] [0x100];
/*0008*/     /*0x84009c042c000000*/     S2R R2, SR_Tid_X;
/*0010*/     /*0x94001c042c000000*/     S2R R0, SR_CTAid_X;
/*0018*/     /*0x10019de218000000*/     MOV32I R6, 0x4;
/*0020*/     /*0x20009ca320044000*/     IMAD R2, R0, c [0x0] [0x8], R2;
/*0028*/     /*0x1020dc435000c000*/     IMUL.U32.U32.HI R3, R2, 0x4;
/*0030*/     /*0x80211c03200d8000*/     IMAD.U32.U32 R4.CC, R2, R6, c [0x0] [0x20];
/*0038*/     /*0x90315c4348004000*/     IADD.X R5, R3, c [0x0] [0x24];
/*0040*/     /*0xa0209c03200d8000*/     IMAD.U32.U32 R2.CC, R2, R6, c [0x0] [0x28];
/*0048*/     /*0x00401c8584000000*/     LD.E R0, [R4];
/*0050*/     /*0xb030dc4348004000*/     IADD.X R3, R3, c [0x0] [0x2c];
/*0058*/     /*0x03f01c003d80cfe0*/     FCMP.LEU R0, RZ, 0x3f800, R0;
/*0060*/     /*0x00201c8594000000*/     ST.E [R2], R0;
/*0068*/     /*0x00001de780000000*/     EXIT;
        ....................................