循环展开C中的内联函数

时间:2015-06-12 11:32:27

标签: c optimization inline icc

我有一个关于C编译器优化以及内联函数何时/如何展开的问题。

我正在开发一个类似于下面例子的数字代码。基本上,my_for()会计算某种模板,并调用op()my_type *arg中每个i的数据执行某些操作。在这里,my_func()包装my_for(),创建参数并将函数指针发送到my_op() ...谁的工作就是为每个修改i个双精度( arg->n)双数组arg->dest[j]

typedef struct my_type {
  int const n;
  double *dest[16];
  double const *src[16];
} my_type;

static inline void my_for( void (*op)(my_type *,int), my_type *arg, int N ) {
  int i;

  for( i=0; i<N; ++i )
    op( arg, i );
}

static inline void my_op( my_type *arg, int i ) {
  int j;
  int const n = arg->n;

  for( j=0; j<n; ++j )
    arg->dest[j][i] += arg->src[j][i];
}

void my_func( double *dest0, double *dest1, double const *src0, double const *src1, int N ) {
  my_type Arg = {
    .n = 2,
    .dest = { dest0, dest1 },
    .src = { src0, src1 }
  };

  my_for( &my_op, &Arg, N );
}

这很好用。这些函数按照它们应该内联,并且代码(几乎)与在单个函数中内联编写所有内容并展开j循环一样高效,而没有任何my_type Arg

这是一个混乱:如果我在int const n = 2;中设置int const n = arg->n;而不是my_op(),则代码会变得与展开的单功能版本一样快。所以,问题是:为什么?如果所有内容都被内联到my_func(),为什么编译器看不到我在字面上定义Arg.n = 2?此外,当我明确地在j循环arg->n上进行绑定时,没有任何改进,这应该看起来就像内联后更快int const n = 2;。我还尝试在任何地方使用my_type const来向编译器发出这种常规信号,但它并不想要展开循环。

在我的数字代码中,这相当于大约15%的性能损失。如果重要,那么n=4和这些j循环会出现在op()的几个条件分支中。

我正在使用icc(ICC)12.1.5 20120612进行编译。我尝试了#pragma unroll。这是我的编译器选项(我错过了什么好的吗?):

-O3 -ipo -static -unroll-aggressive -fp-model precise -fp-model source -openmp -std=gnu99 -Wall -Wextra -Wno-unused -Winline -pedantic

谢谢!

2 个答案:

答案 0 :(得分:3)

嗯,很明显,编译器不够'聪明'足以传播n常量并展开for循环。实际上它是安全的,因为arg->n可以在实例化和使用之间改变。

为了在编译器代之间保持一致的性能并挤出代码的最大值,请手动展开。

像我这样的人在这些情况下做的事情(表现为王)依赖于宏。

宏将在调试版本中“内联”(有用),并且可以使用宏参数进行模板化(到某一点)。作为编译时常量的宏参数保证保持这种方式。

答案 1 :(得分:2)

速度更快,因为你的程序没有为变量分配内存。

如果您不必对未知值执行任何操作,则会将其视为#define constant 2进行类型检查。它们只是在编译时添加的。

你能不能选择两个标签中的一个(我的意思是C或C ++),这让人感到困惑,因为语言对const值的处理方式不同 - C将它们看作普通变量,它们的价值就是&#&# 39; t被改变,并且在C ++中,他们根据上下文分配了内存(如果你需要他们的地址,或者你需要在程序运行时计算它们,那么就会分配内存)。 / p>

来源:&#34;用C ++思考&#34;。没有确切的引用。