我可以优化这段代码吗?

时间:2009-10-29 14:38:02

标签: c optimization

这里是否可以使用任何循环优化技术来缩短执行时间?我需要使用i和j的嵌套循环,因为我需要(i,j)的组合。

编辑:即使我留下“实际”代码,通过这个简单的任务,这在我的双核盒子上占用了大约5秒,而使用实际代码,它需要大约6秒。我尝试用j + = 0替换fn_val + = 0,它需要~1.73s。这可能是什么原因?

# include <stdio.h>
# include <time.h>

int main(int argc, char **argv)
{
        float fn_value=0.0;
        int n=10,i,j;
        unsigned int k;
        clock_t start, end;

        start = clock();
        for(k=0;k<9765625;k++)
        {

                for(i=0;i<n;i++)
                {
                        for(j=i;j<n;j++)
// substitute for an "actual" piece of code
                                fn_value+=0; 
                }
        }
        end= clock();

        printf("Time taken %lf", (double) (end-start) / CLOCKS_PER_SEC);
        return 0;
}

9 个答案:

答案 0 :(得分:2)

如果“doStuff”是线程安全的,那么你可能想要查看OpenMP,因为你可以在不同的线程上为不同的i,j和k索引运行doStuff。

答案 1 :(得分:1)

嗯,它可能很好地并行运行。

答案 2 :(得分:1)

你可以循环展开。实际上,您可以为编译器指定一个参数来展开所有这些循环(实际参数取决于您的编译器)。

我不知道你的“实际代码”是什么,能够为你提供更多信息。如果您正在做一些非常重要的事情,那么您希望优化缓存访问。

另外,您是否正在编译优化? (即gcc中的-O3)

根据你的编辑:

“j + = 0”比“fn_val + = 0”快的原因是因为整数arithemtic比浮点运算快得多。

这就是为什么我们需要实际代码为您提供明智的优化。

答案 3 :(得分:1)

循环展开并不总是比编译器能做得更好,正如其他地方所说的那样,分析并找到时间的去处。

我首先关注的是“实际”代码。有没有什么聪明可以用来“阻止”那里的计算?重复使用以前的anwer来便宜地计算下一个等等。

答案 4 :(得分:1)

由于你的最内层循环只有10次迭代,如果你能组合两个内部循环(总共100次迭代),它会稍微提高你的速度。

答案 5 :(得分:0)

循环本身可能无关紧要,这取决于你在最里面的循环中做了多少工作。

您应该进行一些分析,它会告诉您花费了多少时间,并建议可以在哪些方面进行优化。

答案 6 :(得分:0)

这实际上取决于“替代实际代码”的作用以及代码如何使用值i,j和k。如果使用i,j和k,那么实际上可能没有太多(除了多线程,但如果在数学方程中使用,你可能能够使用一些聪明的代数来降低复杂性/重复性)计算)。另一方面,如果没有使用任何值,那么您可以将其设置为一个将执行指定次数的循环(尽管结果可能因编译器/优化级别而异)。

基本上,如果它们是您需要的最小值,则无法优化循环。此外,这种微优化通常会导致许多错误和不可维护的代码(即使在游戏行业,速度至关重要,我们总是优化最后,然后只有最大的瓶颈),你通常会发现它的算法不是代码本身可以优化(或用具有类似结果的更快算法替换)。您给出的示例除了包含以下内容之外不包含实际算法:

fn_value = 0;
k = 9765625;
n = 10;
i = 10;
j = 10;

因此,上面的代码就是你可以替换整个循环的代码,它会尽可能地优化(假设这些值在其他地方使用,否则你可以完全消除它们。)

答案 7 :(得分:0)

很久以前我听过一次......在某些情况下,循环到零可以更快...

所以: -

for(i=0;i<n;i++)
{
    for(j=i;j<n;j++)
// substitute for an "actual" piece of code
        fn_value+=0; 
}

成为(我认为,总是算错;)): -

for(i=n;i--;)
{
    for(j=n-i;j--;)
// substitute for an "actual" piece of code
        fn_value+=0; 
}

当然,你的循环是倒退的......

我很想知道这是否有所作为!我的直觉是你正在优化错误的东西。

啊哈,一个链接: -  http://www.abarnett.demon.co.uk/tutorial.html#FASTFOR

答案 8 :(得分:0)

您正在使用浮点代码!编译器是浮点代码的垃圾。

这是我做过的一些测量,我正在使用带有默认优化的DevStudio 2005,我稍微更改了代码:

// added to the inner part of the loop
fn_value += j; 

// added a dependancy on fn_value so that the compiler doesn't optimise the 
// whole code down to nothing
printf("Time taken %lf - %f", (double) (end-start) / CLOCKS_PER_SEC, fn_value);

所以,我在大约5s内运行。

现在,我稍微更改了代码:

# include <stdio.h>
# include <time.h>

int main(int argc, char **argv)
{
  int fn_value=0;
  int n=10,i,j;
  unsigned int k;
  clock_t start, end;

  start = clock();
  for(k=0;k<9765625;k++)
  {
    for(i=0;i<n;i++)
    {
      for(j=i;j<n;j++)
        fn_value+=j; 
    }
  }
  end= clock();

  printf("Time taken %lf - %d", (double) (end-start) / CLOCKS_PER_SEC, fn_value);
  return 0;
}

我将fn_value更改为int。现在需要大约一秒钟!因此,添加整数和添加浮点数之间有四秒的开销。然后我用IA32 FPU操作码而不是C代码编写了一个版本,并且得到了大约1.4秒,这比使用整数慢得多。

然后,我使用了C浮点版本,但使fn_value变为double,时间变为1.25s。现在,这让我感到惊讶。它击败了FPU操作码版本,但是,看看解压缩,唯一的区别是纯C版本展开了内循环。

此外,使用浮动时,结果不正确。

这是我的最终测试代码:

# include <stdio.h>
# include <time.h>

void p1 ()
{
  double fn_value=0;//if this is a float, the answer is slightly wrong
  int n=10,i,j;
  unsigned int k;
  clock_t start, end;

  start = clock();
  __asm fldz;
  for(k=0;k<9765625;k++)
  {
    for(i=0;i<n;i++)
    {
      for(j=i;j<n;j++)
        __asm {
          fiadd j
      }
    }
  }
  __asm fstp fn_value;
  end= clock();

  printf("p1: Time taken %lf - %lf\n", (double) (end-start) / CLOCKS_PER_SEC, (double) fn_value);
}

void p2 ()
{
  double fn_value=0;
  int n=10,i,j;
  unsigned int k;
  clock_t start, end;

  start = clock();
  for(k=0;k<9765625;k++)
  {
    for(i=0;i<n;i++)
    {
      for(j=i;j<n;j++)
        fn_value+=j; 
    }
  }
  end= clock();

  printf("p2: Time taken %lf - %lf\n", (double) (end-start) / CLOCKS_PER_SEC, (double) fn_value);
}

void p3 ()
{
  float fn_value=0;
  int n=10,i,j;
  unsigned int k;
  clock_t start, end;

  start = clock();
  for(k=0;k<9765625;k++)
  {
    for(i=0;i<n;i++)
    {
      for(j=i;j<n;j++)
        fn_value+=j; 
    }
  }
  end= clock();

  printf("p3: Time taken %lf - %lf\n", (double) (end-start) / CLOCKS_PER_SEC, (double) fn_value);
}

int main(int argc, char **argv)
{
  p1 ();
  p2 ();
  p3 ();
  return 0;
}

总之,double似乎比float更快。但是,我们需要查看该内部循环的内容,以查看转换浮点类型是否会在特定情况下提供任何加速。

<强>更新

float版本比其他版本慢的原因是因为float版本不断写入并从内存中读取值。双重和手写版本永远不会将值写入RAM。为什么这样做呢。我能想到的主要原因是降低了操作之间fn_value值的精度。在内部,FPU是80位,而浮点数是32位(在C的这个实现中)。为了使值保持在浮点范围内,编译器通过向/从RAM写入和读取值将80位转换为32位,因为据我所知,没有FPU指令对单个FPU寄存器执行此操作。因此,为了保持数学'32位'(类型为float),它会带来巨大的开销。编译器忽略了80位FPU和64位双精度类型之间的差异,并假设程序员想要尽可能多的类型。