循环展开C中的内联函数

Fin*_*ent 8 c optimization inline icc

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

我正在开发一个类似于下面例子的数字代码.基本上,my_for()会计算某种模板并调用每个op()数据.在这里,包装,创建参数并将函数指针发送给...谁的工作是修改每个()双数组的th double .my_type *argimy_func()my_for()my_op()iarg->narg->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 );
}
Run Code Online (Sandbox Code Playgroud)

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

这就是混乱:如果我设置int const n = 2;而不是int const n = arg->n;in 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

谢谢!

egu*_*gur 3

嗯,显然编译器不够“智能”,无法传播常量n并展开for循环。实际上它很安全,因为arg->n可以在实例化和使用之间进行更改。

为了在各代编译器之间获得一致的性能并最​​大限度地利用代码,请手动展开。

像我这样的人在这些情况下(性能为王)所做的就是依赖宏。

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