OpenCL布尔表达式不需要延迟计算
来自OpenCL 2.0规范第6.3章“操作员”,第29页: g。逻辑运算符and(&&)或(| |)操作所有标量和向量内置类型。对于 仅标量内置类型,和(&&&&)将仅在左侧操作数 操作数比较不等于0。仅对于标量内置类型,或(| |)将仅计算 如果左侧操作数比较等于0,则为右侧操作数。对于内置向量类型, 这两个操作数都将求值,并且运算符将按组件应用。如果一个操作数为 一个是标量,另一个是矢量,标量可以进行通常的算术转换 指向向量操作数使用的元素类型。然后将标量类型扩展为向量 与向量操作数具有相同分量数的。手术完成了 组件方面的结果是相同大小的向量 这意味着使用带有逻辑运算符的表达式将导致分支和线程发散,这反过来会导致某些并行平台上的性能损失。例如:OpenCL布尔表达式不需要延迟计算,c,opencl,lazy-evaluation,boolean-expression,C,Opencl,Lazy Evaluation,Boolean Expression,来自OpenCL 2.0规范第6.3章“操作员”,第29页: g。逻辑运算符and(&&)或(| |)操作所有标量和向量内置类型。对于 仅标量内置类型,和(&&&&)将仅在左侧操作数 操作数比较不等于0。仅对于标量内置类型,或(| |)将仅计算 如果左侧操作数比较等于0,则为右侧操作数。对于内置向量类型, 这两个操作数都将求值,并且运算符将按组件应用。如果一个操作数为 一个是标量,另一个是矢量,标量可以进行通常的算术转换 指向向量操作数使用的元素类型。然后将标量类型扩展为向量 与向量操作数具有相
int min_非零(int a,int b)
{
返回(a
这可以部分固定,如下所示:
int min_非零(int a,int b)
{
返回select(b,a,a
内置函数可能使用算术实现,以避免分支(例如线性插值)。但是在和&
中仍然有分支。一个可能更好的方法:
int min_非零(int a,int b)
{
返回select(b,a,(int)(a
但这很快就变得不可读了
因此,我的问题是:是否有更好的方法说服OpenCL编译器放弃布尔表达式的惰性计算(不是全局计算,而是在选定的情况下)?
下面是我在这个问题上的实际实验,不再是一个问题。在某些情况下,仍然需要延迟计算,例如:
if(i
因此,优化器不太可能完全禁用它,或者至少在所有适用的情况下禁用它
我正在查看由NVIDIA 320.49驱动程序生成的一些PTX,它只会优化右侧没有阵列访问的情况:
if(p[i]==n\u end&&i)
回来
编译为单个分支:
setp.ne.s32 %p2, %r17, %r5; // p[i] != n_end
setp.eq.s32 %p3, %r28, 0; // !i
or.pred %p4, %p2, %p3; // (p[i] != n_end || !i) = !(p[i] == n_end && i)
@%p4 bra BB2_3; // branch
ret;
BB2_3:
然而,这:
int n_增量=1;
对于(++i;i
汇编至:
mov.u32 %r29, 1;
BB2_4:
mov.u32 %r6, %r28;
add.s32 %r8, %r6, 1;
ld.param.u32 %r24, [Fill_ColsTailFlags_v0_const_param_0];
setp.ge.u32 %p5, %r8, %r24;
@%p5 bra BB2_6; // branch if i < n_cols_B
shl.b32 %r19, %r6, 2;
ld.param.u32 %r25, [Fill_ColsTailFlags_v0_const_param_1];
add.s32 %r20, %r19, %r25;
ld.const.u32 %r21, [%r20+8];
setp.eq.s32 %p6, %r21, %r5;
@%p6 bra BB2_7; // branch if p[i + 1] == n_end
BB2_6:
shl.b32 %r22, %r5, 2;
ld.param.u32 %r27, [Fill_ColsTailFlags_v0_const_param_2];
add.s32 %r23, %r27, %r22;
st.global.u32 [%r23], %r29;
ret;
BB2_7:
add.s32 %r29, %r29, 1;
mov.u32 %r28, %r8;
bra.uni BB2_4;
mov.u32%r29,1;
BB2_4:
mov.u32%r6,%r28;
添加32%r8,%r6,1;
ld.param.u32%r24,[Fill_ColsTailFlags_v0_const_param_0];
setp.ge.u32%p5、%r8、%r24;
@%p5 bra BB2_6;//分支如果i
它似乎对数组访问感到羞涩,因为它不知道如何根据左边的条件分析数组访问的正确性。在这种情况下,将条件切换为
p[i+1]==n\u end&&i
的顺序将去掉分支。将索引更改为常量i
(其中j=get\u global\u id(0)
在开头初始化)并不能消除分支。第三个版本(以及第二个版本)中的类型转换和括号是不必要的,因此您对可读性的反对有些不合理(我理解其意图,但您基本上要求的是一个行为类似于&
的运算符,唯一的区别是,如果没有大量的括号,您不难理解,我怀疑这一点)。请注意,&
运算符的非直观优先级正是源自该用法(在C的早期发明&&
之前)。出于好奇,在什么样的情况下这些“分支”是性能问题?@ParkYoung-Bae阅读了CUDA执行模型。在那里,线程以32个(称为warp)的包执行,所有线程都必须执行相同的指令。如果它们不执行,那么所有32个线程都需要执行两个分支,并启用适当的屏蔽位,这会使它慢2倍。@ParkYoung Bae和我相信我礼貌地解释了问题所在。我不认为这是过早的,因为我正在优化一些代码,以便在GPU上快速运行,我正在尝试为了使我的指令适应内存延迟,所以每个时钟周期都很重要。我不认为有人真的会费心去编写一开始没有针对性能进行优化的OpenCL代码,那么它就可以留在CPU上了(可能除了令人尴尬的并行算法之外,在这些算法中,不需要任何努力就可以获得合理的加速)。