高效的手动循环展开

时间:2010-11-28 18:12:11

标签: c optimization loops unroll

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

您认为如何修复它?

6 个答案:

答案 0 :(得分:3)

您确定所写的代码是否正确?如果dd是有符号整数类型,则当前代码具有未定义的行为,如果d2未签名或ddd2浮动,则永远不会满足if中的条件点类型。您似乎正在对ki以外的第一个索引j进行搜索失败,其中表达式q2_vect[ k]-q1_vect的溢出会溢出。

至于有效地跳过ij迭代,我只想看看展开的&#34;循环&#34;停止,如果k+1等于ki,则在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 : 0d2时强制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。在这里它可能通过展开来破坏逻辑。