C 高效的手动循环展开

C 高效的手动循环展开,c,optimization,loops,unroll,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; } } 由于lopp依赖于n_n上的每次运行,我应该手工编写代码,直到n_n的最大值 您认为如何修复它?对于第一个问题,您需要在满足条件时不执行

我有一个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;
    }       
}  
由于lopp依赖于n_n上的每次运行,我应该手工编写代码,直到n_n的最大值


您认为如何修复它?

对于第一个问题,您需要在满足条件时不执行循环体。对于这个特殊的问题,您只需将该条件的逻辑否定放在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,则在这种情况下,如果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 至于有效地跳过i和j迭代,我将只查看展开的循环停止的位置,如果k等于i或j,则在k+1处重新启动它。这是假设循环中的代码没有副作用/运行总计,这在编写时是正确的,但我想您可能是想让代码做一些其他的事情,比如平方和


最后,我高度怀疑您是否希望在一开始似乎还没有工作代码的情况下手动展开循环。任何一个好的编译器都可以为您展开循环,但您希望执行的循环展开类型通常会使性能恶化而不是提高。我认为您最好先让代码正常工作,然后测量和查看编译器生成的asm,只有在确定存在问题后才尝试改进它。

您确定编写的代码正确吗?如果dd是有符号整数类型,则当前代码具有未定义的行为;如果d2是无符号类型或dd和d2是浮点类型,则永远不会满足if中的条件。看起来您正在对第一个索引k(而不是i或j)执行中断搜索,其中平方表达式q2_vect[k]-q1_vect溢出

至于有效地跳过i和j迭代,我将只查看展开的循环停止的位置,如果k等于i或j,则在k+1处重新启动它。这是假设循环中的代码没有副作用/运行总计,这在编写时是正确的,但我想您可能是想让代码做一些其他的事情,比如平方和


最后,我高度怀疑您是否希望在一开始似乎还没有工作代码的情况下手动展开循环。任何一个好的编译器都可以为您展开循环,但您希望执行的循环展开类型通常会使性能恶化而不是提高。我认为您最好先让代码正常工作,然后测量和查看编译器生成的asm,并在确定存在问题后尝试改进它。

假设n\u 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);

假设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);

通常,循环展开意味着使循环包含一些迭代,从而减少运行次数。比如说,

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

通常,循环展开意味着使循环包含一些迭代,从而减少运行次数。比如说,

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

编写的这段代码非常不适合SPE,因为它的分支太多。此外,有关所涉及变量类型的信息也会有所帮助;即使使用t,书面测试似乎也相当模糊 He>0修复,但代码看起来可能是C++,使用某种向量类重载运算符,以向量减法和两个向量的操作符*来计算点积。 在SPE上使用这些简单循环的第一件事是让它们至少在内部循环中无分支;i、 e.展开几次,每N次迭代只检查是否提前退出并使用SIMD指令:SPE只有SIMD指令,因此在循环中不使用SIMD处理会立即浪费75%的可用寄存器空间和计算能力。类似地,SPE一次只能加载16字节的对齐qwords,使用较小的数据类型需要额外的工作来洗牌寄存器的内容,以便尝试加载的值最终进入首选插槽

如果k==i | | k==j,则使用以下无分支形式重写循环的第一部分,这是伪代码。它立即适用于ints,但您需要使用intrinsic在浮点上获得按位运算:

dd = q2_vect[k] - q1_vect;
d2 = dd * dd;
d2 &= ~(cmp_equal(k, i) | cmp_equal(k, j));
这里,cmp_equal对应于各自的SPE内部语义:cmp_equala,b==a==b~0u:0。当k==i或k==j时,这迫使d2为零

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

a |= cmp_greater(d2, 0);
并且只检查a是否为非零,以便每隔几次循环迭代就提前退出。如果为d2计算的所有值都是非负的,那么如果您的类型是int、float或实值向量类,则可以进一步简化此过程。只要做:

a |= d2;

最后,只有当所有单个项都为非零时,a才会为非零。但是如果使用int,请小心整数溢出;如果使用float,请小心NaNs溢出。如果您必须处理这些情况,上述简化将破坏代码。

编写的代码非常不适合SPE,因为它非常繁重。此外,有关所涉及变量类型的信息也会有所帮助;写的测试看起来甚至是模糊的,即使有0的修复,但是代码看起来可能是C++,使用某种向量类重载运算符的向量减法和两个向量的操作符*来计算点积。 在SPE上使用这些简单循环的第一件事是让它们至少在内部循环中无分支;i、 e.展开几次,每N次迭代只检查是否提前退出并使用SIMD指令:SPE只有SIMD指令,因此在循环中不使用SIMD处理会立即浪费75%的可用寄存器空间和计算能力。类似地,SPE一次只能加载16字节的对齐qwords,使用较小的数据类型需要额外的工作来洗牌寄存器的内容,以便尝试加载的值最终进入首选插槽

如果k==i | | k==j,则使用以下无分支形式重写循环的第一部分,这是伪代码。它立即适用于ints,但您需要使用intrinsic在浮点上获得按位运算:

dd = q2_vect[k] - q1_vect;
d2 = dd * dd;
d2 &= ~(cmp_equal(k, i) | cmp_equal(k, j));
这里,cmp_equal对应于各自的SPE内部语义:cmp_equala,b==a==b~0u:0。当k==i或k==j时,这迫使d2为零

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

a |= cmp_greater(d2, 0);
并且只检查a是否为非零,以便每隔几次循环迭代就提前退出。如果为d2计算的所有值都是非负的,那么如果您的类型是int、float或实值向量类,则可以进一步简化此过程。只要做:

a |= d2;

最后,只有当所有单个项都为非零时,a才会为非零。但是如果使用int,请小心整数溢出;如果使用float,请小心NaNs溢出。如果您必须处理这些情况,上述简化将破坏代码。

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

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

序列if D2,即使您添加了必要的括号,一个好的编译器也会优化整个if语句,因为C语言没有定义它可能为真的条件。代码似乎试图依赖未定义的行为。不正确:dd==\u invential\u I->dd*dd==-1.0,这是有效的C。抱歉,我输入错误,它是d2>0,所以您正在查找第一个索引,而不是d2不为零的I或j?你为什么要做乘法来得到d2?然后,条件等价于q2_向量[k]=q1_vect…序列if d2,即使您添加了必要的括号,一个好的编译器也会优化整个if语句,因为C语言没有定义它可能为真的条件。代码似乎试图依赖未定义的行为。不对:dd==\u Imaginar

y_I->dd*dd==-1.0,这是有效的C。抱歉,我输入错了,它是d2>0,所以您要查找除I或j之外的第一个索引,其中d2不是零?你为什么要做乘法来得到d2?然后,条件等价于q2_向量[k]=q1\u vect…您确定编译器会自动展开此循环吗?除非n_n是一个常数,否则我认为编译器将很难确定如何展开它。如果dd是虚构的,d2是实数类型,则在没有未定义行为的情况下,条件可能会得到满足:完全展开循环几乎没有任何用处。相反,编译器可能会展开它,每次运行执行8或16次迭代,并使用类似于达夫设备的构造来处理尾部。@Vovanium:haha很公平。希望我能给你更多的+1,因为你注意到了这一点。OP真的使用复杂类型吗?@R..:当然,作者不太可能依赖虚数,但许多人认为x*x>=0是不正确的。我很生气您确定编译器会自动展开此循环吗?除非n_n是一个常数,否则我认为编译器将很难确定如何展开它。如果dd是虚构的,d2是实数类型,则在没有未定义行为的情况下,条件可能会得到满足:完全展开循环几乎没有任何用处。相反,编译器可能会展开它,每次运行执行8或16次迭代,并使用类似于达夫设备的构造来处理尾部。@Vovanium:haha很公平。希望我能给你更多的+1,因为你注意到了这一点。OP真的使用复杂类型吗?@R..:当然,作者不太可能依赖虚数,但许多人认为x*x>=0是不正确的。我很生气这里我不理解或看不到的是编译器如何优化它我不理解或看不到的是编译器如何优化它问题是:n_n是一个运行时值,由用户输入。如果我不知道该值,如何同时手动展开循环n\n次?问题是:n\n是一个运行时值,由用户输入。如果我不知道值,如何同时手动展开循环n\n次?+1表示SIMD。值得注意的是,如果对循环进行向量化,当您将k与i和j进行比较时,k在每个元素中都有不同的值,例如,它将从{0,1,2,3}开始,每次递增{4,4,4}。此外,spu_orx或跨内部函数在这里可能有助于检查是否设置了任何比较结果。SIMD为+1。值得注意的是,如果对循环进行向量化,当您将k与i和j进行比较时,k在每个元素中都有不同的值,例如,它将从{0,1,2,3}开始,每次递增{4,4,4}。此外,spu_orx或cross内在函数在这里可能有助于检查是否设置了任何比较结果。