CUDA中的分支(采取,未采取,分歧)的概念

时间:2014-07-11 08:56:43

标签: cuda branch nsight

在Nsight Visual Studio中,我们将有一个图表来显示"拍摄","未拍摄" "分歧"分支机构。我很困惑"没有采取" "分歧"。 例如

kernel()
{
  if(tid % 32 != 31)
     {...}
  else
     {...}
}

在我看来,当tid %31 == 31处于扭曲中时,会发生分歧,但是"未采取"?

1 个答案:

答案 0 :(得分:4)

来自Nsight Visual Studio Edition用户指南:

  

未采取/采取总计:具有统一控制流决策的已执行分支指令的数量;这是warp的所有活动线程,无论是否接受分支。

     

Diverged :条件导致warp线程产生不同结果的已执行分支指令的总数。具有至少一个参与线程的所有代码路径按顺序执行。较低的数字更好,但是,检查流量控制效率以了解控制流对设备利用率的影响。

现在,让我们考虑以下简单代码,这可能是您目前在测试中考虑的内容:

#include<thrust\device_vector.h>

__global__ void test_divergence(int* d_output) {

    int tid = threadIdx.x;

    if(tid % 32 != 31)
        d_output[tid] = tid;
    else
        d_output[tid] = 30000;

}

void main() {

    const int N = 32;

    thrust::device_vector<int> d_vec(N,0);

    test_divergence<<<2,32>>>(thrust::raw_pointer_cast(d_vec.data()));

}

Nsight生成的分支统计图表如下所示。如您所见, Taken 等于100%,因为所有线程都会进入if语句。令人惊讶的结果是你没有 Diverge 。这可以通过查看内核函数的反汇编代码(为2.1的计算能力编译)来解释:

MOV R1, c[0x1][0x100];
S2R R0, SR_TID.X;                      
SHR R2, R0, 0x1f;                      
IMAD.U32.U32.HI R2, R2, 0x20, R0;      
LOP.AND R2, R2, -0x20;
ISUB R2, R0, R2;
ISETP.EQ.AND P0, PT, R2, 0x1f, PT;
ISCADD R2, R0, c[0x0][0x20], 0x2;
SEL R0, R0, 0x7530, !P0;
ST [R2], R0;
EXIT;

正如您所看到的,编译器能够优化反汇编代码,以便不存在分支,除了由EXIT指令引起的 uniform ,如Greg所指出的那样史密斯在下面的评论中。

enter image description here

编辑:GREG SMITH评论后更复杂的例子

我现在正在考虑以下更复杂的例子

/**************************/
/* TEST DIVERGENCE KERNEL */
/**************************/
__global__ void testDivergence(float *a, float *b)
{
    int tid = threadIdx.x + blockIdx.x * blockDim.x;
    if (tid < 16) a[tid] = tid + 1;
    else b[tid] = tid + 2;
}

/********/
/* MAIN */
/********/
void main() {

    const int N = 64;

    float* d_a; cudaMalloc((void**)&d_a,N*sizeof(float));
    float* d_b; cudaMalloc((void**)&d_b,N*sizeof(float));

    testDivergence<<<2,32>>>(d_a, d_b);

}

这是分支统计

enter image description here

虽然这是反汇编的代码

           MOV R1, c[0x1][0x100];                   
           S2R R0, SR_CTAID.X;                         R0 = blockIdx.x
           S2R R2, SR_TID.X;                           R0 = threadIdx.x
           IMAD R0, R0, c[0x0][0x8], R2;               R0 = threadIdx.x + blockIdx.x * blockDim.x
           ISETP.LT.AND P0, PT, R0, 0x10, PT;          Checks if R0 < 16 and puts the result in predicate register P0
/*0028*/   @P0 BRA.U 0x58;                                 If P0 = true, jumps to line 58
          @!P0 IADD R2, R0, 0x2;                           If P0 = false, R2 = R0 + 2
          @!P0 ISCADD R0, R0, c[0x0][0x24], 0x2;           If P0 = false, calculates address to store b[tid] in global memory
          @!P0 I2F.F32.S32 R2, R2;                                                "
          @!P0 ST [R0], R2;                                                       "
/*0050*/  @!P0 BRA.U 0x78;                                 If P0 = false, jumps to line 78
/*0058*/   @P0 IADD R2, R0, 0x1;                           R2 = R0 + 1
           @P0 ISCADD R0, R0, c[0x0][0x20], 0x2;
           @P0 I2F.F32.S32 R2, R2;
           @P0 ST [R0], R2;
/*0078*/       EXIT;

可以看出,现在我们在反汇编代码中有两条BRA指令。从上图中可以看出,每个warp都会陷入3个分支(一个用于EXIT和两个BRA s)。两个warp都有1 采用分支,因为所有线程均匀地进入EXIT指令。第一个warp有2 没有分支,因为两个BRA s路径在经线上没有统一遵循。第二个warp有1 未采用分支和1 采取分支,因为所有的warp线程统一遵循两个BRA中的一个秒。我会说再次分叉*等于零,因为两个分支中的指令完全相同,尽管在不同的操作数上执行。