我有这个C代码:
for (k = 0; k < n_n; k++) {
if (k == i || k == j) continue;
dd=q2_vect[k]-q1_vect;
d2=dd*dd;
if (d2<0) {
a=1;
break;
}
}
出于编译器优化的原因(在Cell处理器的SPE上),我需要手动解开这个,所以我尝试了:
dd=q2_vect[0]-q1_vect;
d2=dd*dd;
if (d2<0)goto done;
dd=q2_vect[1]-q1_vect;
d2=dd*dd;
if (d2<0)goto done;
dd=q2_vect[2]-q1_vect;
d2=dd*dd;
if (d2<0)goto done;
.....
.....
// end
goto notdone;
done:
ok=0;
notdone:
.....
但我不知道如何处理
if (k == i || k == j) continue;
并且事实上lopp取决于每次在“n_n”上运行,并且我应该编写代码,因为最大值“n_n”会得到很多次。
您认为如何修复它?
答案 0 :(得分:3)
您确定所写的代码是否正确?如果dd
是有符号整数类型,则当前代码具有未定义的行为,如果d2
未签名或dd
和d2
浮动,则永远不会满足if中的条件点类型。您似乎正在对k
或i
以外的第一个索引j
进行搜索失败,其中表达式q2_vect[ k]-q1_vect
的溢出会溢出。
至于有效地跳过i
和j
迭代,我只想看看展开的&#34;循环&#34;停止,如果k+1
等于k
或i
,则在j
重新启动。这假设你的循环中的代码没有副作用/运行总数,这是正如所写的那样,但我希望你可能有意为代码做其他事情(比如求平方)。
最后,我高度怀疑你希望在你甚至看起来没有工作代码时手动展开循环。任何好的编译器都可以为你展开循环,但是通常展开你的循环类型会使性能变差而不是更好。我认为你最好先让你的代码正常工作,然后测量(并查看编译器生成的asm),然后在之后尝试改进 确定存在问题。
答案 1 :(得分:1)
这些编写的代码非常不适合SPE,因为它非常重要。此外,有关变量类型的信息也会有所帮助;所写的测试看起来相当模糊(即使使用>0
修复),但代码看起来可能是C ++使用某种向量类重载operator -
来表示向量减法和{{1}两个向量来计算点积。
在SPE上使用这些简单循环的第一件事就是让它们无分支(至少内部循环;即展开几次并且只检查每N次迭代的早期退出)并使用SIMD指令:SPE 只有SIMD指令,因此不在循环中使用SIMD处理会立即浪费75%的可用寄存器空间和计算能力。类似地,SPE一次只能加载对齐的qwords(16字节),使用较小的数据类型需要额外的工作来混洗寄存器的内容,以便你试图加载的值最终在“首选插槽”中。 / p>
你通过使用以下无分支形式重写循环的第一部分来摆脱operator *
(这是伪代码。它立即适用于整数,但你需要使用内在函数来获得按位ops on floats):
if (k == i || k == j)
这里,dd = q2_vect[k] - q1_vect;
d2 = dd * dd;
d2 &= ~(cmp_equal(k, i) | cmp_equal(k, j));
对应于各自的SPE内在函数(语义:cmp_equal
)。这会在cmp_equal(a,b) == (a == b) ? ~0u : 0
或d2
时强制k == i
为零。
要避免内循环中的k == j
分支,请执行以下操作:
if (d2 > 0)
并且每次循环迭代只检查a |= cmp_greater(d2, 0);
是否非零(提前)。如果为a
计算的所有值都是非负的(如果您的类型是整数,浮点数或实值向量类,则会出现这种情况),您可以进一步简化此操作。只是做:
d2
最后,如果所有单个字词都非零,则a |= d2;
将仅为非零值。但要小心整数溢出(如果你使用整数)和NaN(如果你使用浮点数)。如果您必须处理这些情况,上述简化将破坏代码。
答案 2 :(得分:0)
对于第一个问题,您需要在满足条件时不“执行”循环体。对于这个特殊问题,您可以将该条件的逻辑否定放在if
语句的条件中。
正常展开是一个因素;展开的代码仍然存在于循环中(除非已知循环边界非常小)。此外,您需要在循环外执行工作的“余数”(对应于问题大小的其余部分除以展开因子)。
所以,循环展开的一个例子:
for (i = 0; i < n; ++i) do_something(i);
可以按因子2展开:
for (i = 0; i < n-1; i += 2) { do_something(i); do_something(i+1); }
for (; i < n; ++i) do_something(i);
其中第二个循环执行“余数”(它还将i
设置为与展开循环相同的东西,但如果此后不需要i
,则整行可以对于这种情况,只是if (i < n) etc
。
答案 3 :(得分:0)
假设n_n是一个编译时常量,循环可以像这样简单地展开:
do
{
k=0
if (k == i || k == j)
;
else
{
dd=q2_vect[ k]-q1_vect;
d2=dd*dd;
if (d2<0)
{
a=1;
break;
}
}
k=1
if (k == i || k == j)
;
else
{
dd=q2_vect[ k]-q1_vect;
d2=dd*dd;
if (d2<0)
{
a=1;
break;
}
}
/* and so on, n_n times */
k= n_n-1
if (k == i || k == j)
;
else
{
dd=q2_vect[ k]-q1_vect;
d2=dd*dd;
if (d2<0)
{
a=1;
break;
}
}
} while (0);
基本上,继续之后的所有内容都会进入if语句的else
部分
编辑:由于n_n
不是编译时常量,您仍然可以通过在循环中循环执行多次运行来展开循环,然后使用switch-case语句完成。事实上,您可以将它们组合起来,这称为Duff's device.
#define LOOP_BODY \
do{ \
if (k == i || k == j) \
; \
else \
{ \
dd=q2_vect[ k]-q1_vect; \
d2=dd*dd; \
if (d2<0) \
{ \
a=1; \
break; \
} \
} while (0)
k = 0;
switch(n_n % 8)
{
case 0: for (; k < n_n; k++) { LOOP_BODY; k++;
case 7: LOOP_BODY; k++;
case 6: LOOP_BODY; k++;
case 5: LOOP_BODY; k++;
case 4: LOOP_BODY; k++;
case 3: LOOP_BODY; k++;
case 2: LOOP_BODY; k++;
case 1: LOOP_BODY; k++;}
}
答案 4 :(得分:0)
通常循环展开意味着使循环包含一些迭代,这样它运行的次数就会减少。例如,
for(i=0;i<count;i++) {
printf("%d", i);
}
可以展开到
i=0;
if(count%2==1) {
printf("%d", i);
i=1;
}
while(i<count) {
printf("%d", i);
printf("%d", i+1);
i+=2;
}
答案 5 :(得分:0)
展开此循环对此没有多大帮助。内循环软件展开有助于软件流水线化指令,以在运行时实现更高的IPC。在这里它可能通过展开来破坏逻辑。