C++ 任何人都可以修改以下代码以避免使用“if”吗?
我目前正在cuda上工作,我被下面的代码卡住了。该代码最初是用matlab编写的,我正在尝试使用cuda重新编写:C++ 任何人都可以修改以下代码以避免使用“if”吗?,c++,c,logic,C++,C,Logic,我目前正在cuda上工作,我被下面的代码卡住了。该代码最初是用matlab编写的,我正在尝试使用cuda重新编写: Pv = 0; Nv =0; [LOOP] v1 = l(li); v2 = l(li+1); if((v1>0) && (v2>0)) Pv = Pv + 1; elseif((v1<0) && (v2<0)) Nv = Nv +1; elseif((v1>0) && (v2<0)
Pv = 0; Nv =0;
[LOOP]
v1 = l(li);
v2 = l(li+1);
if((v1>0) && (v2>0))
Pv = Pv + 1;
elseif((v1<0) && (v2<0))
Nv = Nv +1;
elseif((v1>0) && (v2<0))
r = v1/(v1-v2);
Pv = Pv + r;
Nv = Nv + 1 - r;
elseif((v1<0) && (v2>0))
r = v2/(v2-v1);
Pv = Pv + r;
Nv = Nv + 1 - r;
end
[LOOP END]
但是,在cuda体系结构中,如果表达式有时很昂贵,我相信有一些方法可以避免使用它,尽管我现在无法理解
代码的主要目的是计算正区间与负区间的比值,并分别相加。在大多数情况下,v1和v2有相同的符号,但一旦它们有不同的符号,我就必须使用一堆if,甚至abs来处理这种情况
那么,有谁能帮我用C重新编写代码,同时尽可能少地使用?利用您使用的每个表达式都给出一个0或1二进制表达式的事实,我可以将其归结为一个三元运算符:
// r is 0 if not case 3 or 4
r = (v1==v2)?0:((v1>v2)*v1-(v2>v1)*v2)/(v1-v2) * (v1*v2<0);
Pv += r + // cases 3 & 4
(v1>0) && (v2>0); // case 1
Nv += r + // cases 3 & 4
(v1<=0) || (v2<=0); // cases 2, 3 and 4
编辑:进一步优化未经测试
然而,这听起来很像过早的优化。是否真的是if语句的数量会引起问题?利用您使用的每个表达式都给出一个0或1二进制表达式的事实,我可以将其归结为一个三元运算符:
// r is 0 if not case 3 or 4
r = (v1==v2)?0:((v1>v2)*v1-(v2>v1)*v2)/(v1-v2) * (v1*v2<0);
Pv += r + // cases 3 & 4
(v1>0) && (v2>0); // case 1
Nv += r + // cases 3 & 4
(v1<=0) || (v2<=0); // cases 2, 3 and 4
编辑:进一步优化未经测试
然而,这听起来很像过早的优化。是否真的是if语句的数量会导致问题?添加到:
这个
正如a中abligh所指出的,在ra初始化过程中,我们可能会得到除以零的结果,因此我们只想进行适当的计算:
double r0(double v1, double v2)
{
return 0;
}
double r1(double v1, double v2)
{
return ((v1 > v2) * v1 - (v2 > v1) * v2) / (v1 - v2) * (v1 * v2 < 0);
}
double (*rf[2])(double v1, double v2) = {r0, r1};
...
r = rf[fabs(v1 - v2) < EPSILON](v1, v2); /* With EPSILON like 0.00001 or what ever accuracy is needed. */
添加到的:
这个
正如a中abligh所指出的,在ra初始化过程中,我们可能会得到除以零的结果,因此我们只想进行适当的计算:
double r0(double v1, double v2)
{
return 0;
}
double r1(double v1, double v2)
{
return ((v1 > v2) * v1 - (v2 > v1) * v2) / (v1 - v2) * (v1 * v2 < 0);
}
double (*rf[2])(double v1, double v2) = {r0, r1};
...
r = rf[fabs(v1 - v2) < EPSILON](v1, v2); /* With EPSILON like 0.00001 or what ever accuracy is needed. */
由于您指出,同号非零操作数的情况是最常见的情况,因此最好使用分支处理罕见的情况3和4,特别是因为它们需要昂贵的除法运算,而我们不希望在公共快速路径中执行。考虑到最小值和最大值以及三元运算符是由GPU中的硬件直接支持的,并且GPU对谓词有广泛的支持,我建议进行如下所示的轻微重写。我已经使用从集合{-0.0f,-3.0f,-5.0f,0.0f,3.0f,5.0f}中抽取的两个元素的随机组合来测试这一点,以确保涵盖v1==0或v2==0的情况
float Nv, Pv, v1, v2;
float r;
if ((v1 > 0) && (v2 > 0)) {
Pv = Pv + 1;
}
if ((v1 < 0) && (v2 < 0)) {
Nv = Nv + 1;
}
if (((v1 > 0) && (v2 < 0)) || ((v1 < 0) && (v2 > 0))) { // rare case
float s = min (v1, v2);
float t = max (v1, v2);
r = t / (t - s);
Pv = Pv + r;
Nv = Nv + 1 - r;
}
由于您指出,同号非零操作数的情况是最常见的情况,因此最好使用分支处理罕见的情况3和4,特别是因为它们需要昂贵的除法运算,而我们不希望在公共快速路径中执行。考虑到最小值和最大值以及三元运算符是由GPU中的硬件直接支持的,并且GPU对谓词有广泛的支持,我建议进行如下所示的轻微重写。我已经使用从集合{-0.0f,-3.0f,-5.0f,0.0f,3.0f,5.0f}中抽取的两个元素的随机组合来测试这一点,以确保涵盖v1==0或v2==0的情况
float Nv, Pv, v1, v2;
float r;
if ((v1 > 0) && (v2 > 0)) {
Pv = Pv + 1;
}
if ((v1 < 0) && (v2 < 0)) {
Nv = Nv + 1;
}
if (((v1 > 0) && (v2 < 0)) || ((v1 < 0) && (v2 > 0))) { // rare case
float s = min (v1, v2);
float t = max (v1, v2);
r = t / (t - s);
Pv = Pv + r;
Nv = Nv + 1 - r;
}
我同意这似乎是过早的优化。这个代码段是否更快取决于数据:它在聚合情况下做了更多的工作。@Jez:的确如此。我只计算了r的一个值,使它稍微不那么糟糕。您是否也尝试过优化源空间-还有剩余的空间可以移除-由于我不敢在你的答案中乱涂乱画,请看看我的答案。我同意这似乎是过早的优化。这个代码段是否更快取决于数据:它在聚合情况下做了更多的工作。@Jez:的确如此。我只计算了r的一个值,使它稍微不那么糟糕。您是否也尝试过优化源空间-还有剩余的空间可以移除-由于我不敢在你的答案中乱涂乱画,请看看我的答案。双ra[1]不应该是双ra[2]吗?这本书的重点是什么!!在ra中[!!v1==v2]?@Jez:Aargh,是的,当然。固定的谢谢。:-}CUDA也不喜欢线程本地数组的动态索引,所以我希望ra放在堆栈帧中,而不是寄存器中。这几乎肯定会比分支对性能的影响更大。@Jez:the!!只是糖。它确保v1==v2的计算结果为真布尔值。在执行硬代码C时,这并不是必须的。但是,第二次看你是对的,这是多余的。用v1-v2*v1*v2=0除法怎么样?这会使除法安全,如果不想被除法,那么输出就没有多大关系,因为它只在v1和v2的符号不同时使用。避免比较浮点值是否相等。double ra[1]不应该是double ra[2]吗?这本书的重点是什么!!在ra中[!!v1==v2]?@Jez:Aargh,是的,当然。固定的谢谢。:-}CUDA也不喜欢线程本地数组的动态索引,所以我希望ra放在堆栈框架中,而不是r
登记员。这几乎肯定会比分支对性能的影响更大。@Jez:the!!只是糖。它确保v1==v2的计算结果为真布尔值。在执行硬代码C时,这并不是必须的。但是,第二次看你是对的,这是多余的。用v1-v2*v1*v2=0除法怎么样?这会使除法安全,如果不想被除法,那么输出就没有多大关系,因为它只在v1和v2的符号不同时使用。避免比较浮点值是否相等。Pv、Nv、v1、v2的类型是什么?浮动,双人?CUDA编译器可能使用if转换来断言代码,或者将这些赋值转换为三元运算符,因此尝试手动将其转换为无分支代码可能是过早的优化。使用cuobjdump-dump SASS检查生成的sas,并分析内核以确定分支分歧是否是一个很大的问题,这将非常有用。@njuffa它们都是浮点变量。我从来没有想过检查二进制代码,如果可能的话我会试试。Pv、Nv、v1、v2的类型是什么?浮动,双人?CUDA编译器可能使用if转换来断言代码,或者将这些赋值转换为三元运算符,因此尝试手动将其转换为无分支代码可能是过早的优化。使用cuobjdump-dump SASS检查生成的sas,并分析内核以确定分支分歧是否是一个很大的问题,这将非常有用。@njuffa它们都是浮点变量。我从来没有想过检查二进制代码,如果可能的话我会试试。
/*0008*/ FSETP.LT.AND P2, PT, RZ, c[0x0][0x2c], PT;
/*0010*/ FSETP.GT.AND P3, PT, RZ, c[0x0][0x28], PT;
/*0018*/ FSETP.LT.AND P1, PT, RZ, c[0x0][0x28], PT;
/*0020*/ MOV32I R4, 0x3f800000;
/*0028*/ FSETP.GT.AND P4, PT, RZ, c[0x0][0x2c], PT;
/*0030*/ PSETP.AND.AND P5, PT, P3, P2, PT;
/*0038*/ PSETP.AND.AND P0, PT, P1, P2, PT;
/*0040*/ FADD R0, R4, c[0x0][0x24];
/*0048*/ PSETP.AND.AND P2, PT, P3, P4, PT;
/*0050*/ FADD R4, R4, c[0x0][0x20];
/*0058*/ PSETP.AND.OR P1, PT, P1, P4, P5;
/*0060*/ MOV R2, c[0x0][0x30];
/*0068*/ MOV R3, c[0x0][0x34];
/*0070*/ SEL R0, R0, c[0x0][0x24], P0;
/*0078*/ SEL R6, R4, c[0x0][0x20], P2;
/*0080*/ @!P1 BRA 0xc8;
/*0088*/ MOV R4, c[0x0][0x28];
/*0090*/ FMNMX R5, R4, c[0x0][0x2c], PT;
/*0098*/ FMNMX R4, R4, c[0x0][0x2c], !PT;
/*00a0*/ FADD R5, R4, -R5;
/*00a8*/ CAL 0xe0; // division
/*00b0*/ FADD R5, R6, 1;
/*00b8*/ FADD R0, R0, R4;
/*00c0*/ FADD R6, R5, -R4;
/*00c8*/ FADD R0, R0, R6;