高效的手动循环展开

我有这个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”会得到。

你怎么认为它可以修复?

你确定编写的代码是否正确? 如果dd是有符号整数类型,则当前代码具有未定义的行为,如果d2是无符号的,或者ddd2是浮点类型,则永远不满足if中的条件。 看起来你正在搜索除了ij之外的第一个索引k ,其中平方表达式q2_vect[ k]-q1_vect溢出。

至于有效地跳过ij迭代,我只想看看展开的“循环”停止的位置,如果k等于ij ,则在k+1重新启动它。 这假设你的循环中的代码没有副作用/运行总数,这是正如所写的那样,但我希望你可能有意为代码做其他事情(比如求和方块)。

最后,我非常怀疑你希望在你甚至似乎没有工作代码开始时手动展开循环。 任何好的编译器都可以为你展开循环,但是通常你展开的循环类型会使性能变差而不是更好。 我认为你最好先让你的代码正常工作,然后测量(并查看编译器生成的asm),并你确定存在问题后再尝试改进。

所写的代码对于SPE来说是相当不合适的,因为它的分支很重。 此外,有关变量类型的信息也会有所帮助; 所写的测试似乎相当模糊(即使使用>0修复),但代码看起来可能是C ++使用某种重载operator -的向量类operator -表示向量减法和两个向量的operator *来计算点积。

在SPE上使用这些简单循环的第一件事就是让它们无分支(至少内部循环;即展开几次并且只检查每N次迭代的早期退出)并使用SIMD指令:SPE 只有SIMD因此,不要在循环中使用SIMD处理会浪费75%的可用寄存器空间和计算能力。 类似地,SPE一次只能加载对齐的qwords(16字节),使用较小的数据类型需要额外的工作来重新调整寄存器的内容,以便您尝试加载的值最终在“首选插槽”中。

通过使用以下无分支forms重写循环的第一部分来消除if (k == i || k == j) (这是伪代码。它立即适用于整数,但你需要使用intrinsics在浮点数上获取按位运算):

 dd = q2_vect[k] - q1_vect; d2 = dd * dd; d2 &= ~(cmp_equal(k, i) | cmp_equal(k, j)); 

这里, cmp_equal对应于各自的SPE内在函数(语义: cmp_equal(a,b) == (a == b) ? ~0u : 0 )。 当k == ik == j时,这迫使d2为零。

要避免内循环中的if (d2 > 0)分支,请执行以下操作:

 a |= cmp_greater(d2, 0); 

并且只检查a是否为非零(早期)每几次循环迭代。 如果为d2计算的所有值都是非负的(如果您的类型是整数,浮点数或实值向量类,则会出现这种情况),您可以进一步简化此操作。 做就是了:

 a |= d2; 

最后,如果所有单个术语都非零,则只有非零。 但要注意整数溢出(如果你使用整数)和NaN(如果你使用浮点数)。 如果您必须处理这些情况,上述简化将破坏代码。

通常,循环展开意味着使循环包含一些迭代,这样它运行的次数更少。 例如,

 for(i=0;i 

可以展开到

 i=0; if(count%2==1) { printf("%d", i); i=1; } while(i 

对于第一个问题,您需要在满足条件时不“执行”循环体。 对于这个特殊问题,您可以将该条件的逻辑否定放在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为此案件)。

假设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的设备。

 #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++;} } 

展开这个循环在这里没有多大帮助。 内循环软件展开有助于软件流水线化指令,以在运行时实现更高的IPC。 在这里它可能通过展开来破坏逻辑。