问题 分支和谓词指令


第5.4.2节 “CUDA C编程指南”规定,分支差异由“分支指令”处理,或者在某些条件下由“预测指令”处理。我不明白两者之间的区别,以及为什么一个导致比另一个更好的性能。

这个评论 表明分支指令导致更多的执行指令,由于“分支地址解析和获取”而导致停顿,以及由于“分支本身”和“保留发散”而导致的开销,而预测指令仅产生“指令执行”进行条件测试的延迟并设置谓词“。为什么?


7415
2018-05-17 15:37


起源



答案:


指令预测意味着线程根据谓词有条件地执行指令。谓词为true的线程执行指令,其余的则不执行任何操作。

例如:

var = 0;

// Not taken by all threads
if (condition) {
    var = 1;
} else {
    var = 2;
}

output = var;

会导致(不是实际的编译器输出):

       mov.s32 var, 0;       // Executed by all threads.
       setp pred, condition; // Executed by all threads, sets predicate.

@pred  mov.s32 var, 1;       // Executed only by threads where pred is true.
@!pred mov.s32 var, 2;       // Executed only by threads where pred is false.
       mov.s32 output, var;  // Executed by all threads.

总而言之,这是3条指令 if,没有分支。效率很高。

带分支的等效代码如下所示:

       mov.s32 var, 0;       // Executed by all threads.
       setp pred, condition; // Executed by all threads, sets predicate.

@!pred bra IF_FALSE;         // Conditional branches are predicated instructions.
IF_TRUE:                    // Label for clarity, not actually used.
       mov.s32 var, 1;
       bra IF_END;
IF_FALSE:
       mov.s32 var, 2;
IF_END:
       mov.s32 output, var;

注意它是多长时间(5条说明 if)。条件分支需要禁用部分warp,执行第一个路径,然后回滚到warp发散的点并执行第二个路径直到两个收敛。它需要更长的时间,需要额外的簿记,更多的代码加载(特别是在有许多指令要执行的情况下),因此需要更多的内存请求。所有这些都使得分支比简单的预测更慢。

实际上,在这个非常简单的条件赋值的情况下,编译器可以做得更好,只有2条指令 if

mov.s32 var, 0;       // Executed by all threads.
setp pred, condition; // Executed by all threads, sets predicate.
selp var, 1, 2, pred; // Sets var depending on predicate (true: 1, false: 2).

11
2018-05-17 16:24



如果我理解正确:在两种情况下,warp必须顺序执行两个路径,但由于额外的分支指令,分支差异惩罚甚至更高 bra 说明?你能详细说明簿记方面,难道不是所有的线程都有自己的程序计数器吗?顺便问一下,不是吗? @!pred bra IF_FALSE;  在带分支的代码中? - lodhb
在SM 5.x和旧设备上,warp中的所有线程共享相同的程序计数器。 warp维护活动通道(线程)的掩码。每个线程都有自己的谓词和控制代码寄存器。分支的代价是(a)解析分支的延迟和(b)获取下一条指令的延迟。如果编译器比上述两个成本便宜,它将使用预测。这通常是6-10条指令。如果分支是多向发散的,则成本较高,因为分支(BRX)必须多次执行。 - Greg Smith


答案:


指令预测意味着线程根据谓词有条件地执行指令。谓词为true的线程执行指令,其余的则不执行任何操作。

例如:

var = 0;

// Not taken by all threads
if (condition) {
    var = 1;
} else {
    var = 2;
}

output = var;

会导致(不是实际的编译器输出):

       mov.s32 var, 0;       // Executed by all threads.
       setp pred, condition; // Executed by all threads, sets predicate.

@pred  mov.s32 var, 1;       // Executed only by threads where pred is true.
@!pred mov.s32 var, 2;       // Executed only by threads where pred is false.
       mov.s32 output, var;  // Executed by all threads.

总而言之,这是3条指令 if,没有分支。效率很高。

带分支的等效代码如下所示:

       mov.s32 var, 0;       // Executed by all threads.
       setp pred, condition; // Executed by all threads, sets predicate.

@!pred bra IF_FALSE;         // Conditional branches are predicated instructions.
IF_TRUE:                    // Label for clarity, not actually used.
       mov.s32 var, 1;
       bra IF_END;
IF_FALSE:
       mov.s32 var, 2;
IF_END:
       mov.s32 output, var;

注意它是多长时间(5条说明 if)。条件分支需要禁用部分warp,执行第一个路径,然后回滚到warp发散的点并执行第二个路径直到两个收敛。它需要更长的时间,需要额外的簿记,更多的代码加载(特别是在有许多指令要执行的情况下),因此需要更多的内存请求。所有这些都使得分支比简单的预测更慢。

实际上,在这个非常简单的条件赋值的情况下,编译器可以做得更好,只有2条指令 if

mov.s32 var, 0;       // Executed by all threads.
setp pred, condition; // Executed by all threads, sets predicate.
selp var, 1, 2, pred; // Sets var depending on predicate (true: 1, false: 2).

11
2018-05-17 16:24



如果我理解正确:在两种情况下,warp必须顺序执行两个路径,但由于额外的分支指令,分支差异惩罚甚至更高 bra 说明?你能详细说明簿记方面,难道不是所有的线程都有自己的程序计数器吗?顺便问一下,不是吗? @!pred bra IF_FALSE;  在带分支的代码中? - lodhb
在SM 5.x和旧设备上,warp中的所有线程共享相同的程序计数器。 warp维护活动通道(线程)的掩码。每个线程都有自己的谓词和控制代码寄存器。分支的代价是(a)解析分支的延迟和(b)获取下一条指令的延迟。如果编译器比上述两个成本便宜,它将使用预测。这通常是6-10条指令。如果分支是多向发散的,则成本较高,因为分支(BRX)必须多次执行。 - Greg Smith