C语言内联函数中的循环展开

C语言内联函数中的循环展开,c,optimization,inline,icc,C,Optimization,Inline,Icc,我有一个关于C编译器优化以及内联函数中的循环何时/如何展开的问题 我正在开发一个数字代码,它的功能类似于下面的示例。基本上,my_for()将计算某种模具,并调用op()对每个i的my_type*arg中的数据进行处理。在这里,my_func()包装my_for(),创建参数并将函数指针发送到my_op()。。。谁的工作是为每个(arg->n)双数组arg->dest[j]修改ith double typedef struct my_type { int const n; double

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

我正在开发一个数字代码,它的功能类似于下面的示例。基本上,
my_for()
将计算某种模具,并调用
op()
对每个
i
my_type*arg
中的数据进行处理。在这里,
my_func()
包装
my_for()
,创建参数并将函数指针发送到
my_op()
。。。谁的工作是为每个(
arg->n
)双数组
arg->dest[j]
修改
i
th double

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 );
}
typedef结构我的类型{
int const n;
双*dest[16];
双常数*src[16];
}我的类型;
静态内联void my_for(void(*op)(my_type*,int),my_type*arg,int N){
int i;
对于(i=0;in;
对于(j=0;jdest[j][i]+=arg->src[j][i];
}
void my_func(双*dest0,双*dest1,双常量*src0,双常量*src1,int N){
my_类型参数={
.n=2,
.dest={dest0,dest1},
.src={src0,src1}
};
my_for(&my_op,&Arg,N);
}
这很好。函数应该是内联的,代码的效率(几乎)与在单个函数中内联编写所有内容并展开
j
循环一样,没有任何类型的
my_type Arg

这里有一个混淆:如果我在
my_op()
中设置
int const n=2;
而不是
int const n=arg->n;
,那么代码的速度就会和展开的单函数版本一样快。因此,问题是:为什么?如果所有内容都被内联到
my_func()中
,为什么编译器看不到我在字面上定义
Arg.n=2
?此外,当我显式地在
j
循环
Arg->n
上进行绑定时,没有任何改进,这应该看起来就像内联后更快的
int const n=2;
。我还尝试使用
my_type const
everywhere向编译器发出这个常量的信号,但它只是不想展开循环

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

我正在使用icc(icc)12.1.5 20120612进行编译。我尝试了
#pragma unroll
。以下是我的编译器选项(我是否错过了任何好的选项?)

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


谢谢!

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

如果您不必对未知值执行任何操作,它们将被视为是
#通过类型检查定义常量2
。它们只是在编译时添加的

<>你能选择这两个标签中的一个(我是C还是C++),这是令人困惑的,因为语言对待<代码> const 值不同。C把它们当作普通变量,它们不能改变,C++中,它们根据上下文而分配或不分配内存。(如果您需要它们的地址,或者在程序运行时需要计算它们,则会分配内存)


源代码:“用C++”思考。没有确切的引语。

好吧,显然编译器不够“聪明”来传播
n
常量并展开
for
循环。实际上它很安全,因为
arg->n
可以在实例化和使用之间变化

为了在编译器各代之间保持一致的性能,并最大限度地压缩代码,请手动展开

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


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

您正在查看生成的代码吗?有多远查找内联时在编译时已知的值是一个困难的决定。看起来您遇到了编译器的限制。将
n
作为显式函数参数传递可能会提高几率。我想知道如果您交换维度,它是否不会获得更快的速度。正如现在所给出的,您可能对c没有什么好处ache行和突发填充(您可以使用memcpy,它已经进行了高度优化)。此外,使用initializer填充结构是一个gcc扩展(希望您注意到这一点-对我来说不是问题)@Olaf在真实的模拟代码中有很多额外的计算和条件——所有这些都与j无关,不需要重新计算。虽然N~1024^3,这实际上比相反的速度快了一个数量级。感谢结构初始值设定项信息,我不知道。幸运的是icc不知道你似乎不介意…你是如何确定
内联的
函数是由编译器内联的?顺便说一句,我觉得这篇文章很有趣,虽然有点老了,它描述了一些编译器操作。你说的“那应该是注释”是什么意思?我明白了。但我不能发表评论,因为我还没有得到50分。好吧,这应该是一个评论而不是一个答案。至少部分内容(确实有点无组织).好的。我会修改一下结构,你现在可以评论一下,所以下次记得。显然,你的问题已经吸引了两位投票人。C预处理器宏在使用调试器时是个麻烦事。宏是宏,而不是模板,即使它们似乎共享某些类型的行为。你不需要对宏进行模板化,你只需要提供ar即使在宏中使用宏,也会出现问题。宏由C预处理器展开,生成文本,然后输入C编译器。这是70年代的事