在Nsight Visual Studio中,我们将有一个图表来显示"拍摄","未拍摄" "分歧"分支机构。我很困惑"没有采取" "分歧"。 例如
kernel()
{
if(tid % 32 != 31)
{...}
else
{...}
}
在我看来,当tid %31 == 31
处于扭曲中时,会发生分歧,但是"未采取"?
答案 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所指出的那样史密斯在下面的评论中。
编辑: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);
}
这是分支统计图
虽然这是反汇编的代码
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
中的一个秒。我会说再次分叉*等于零,因为两个分支中的指令完全相同,尽管在不同的操作数上执行。