我有一个关于 C 编译器优化以及何时/如何展开内联函数中的循环的问题。
我正在开发一个数字代码,它的功能类似于下面的示例。基本上,my_for()
会计算某种模板并调用 op()
对 my_type *arg
中的数据做些什么对于每个 i
.在这里,my_func()
包裹 my_for()
, 创建参数并将函数指针发送到 my_op()
...谁的工作是修改 i
每个 ( arg->n
) double 组 arg->dest[j]
的第 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 );
}
这很好用。这些函数按照它们应该的方式进行内联,并且代码(几乎)与在单个函数中内联编写所有内容并展开 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
谢谢!
最佳答案
很明显,编译器不够“聪明”,无法传播 n
常量并展开 for
循环。实际上它是安全的,因为 arg->n
可以在实例化和使用之间改变。
为了在各代编译器中获得一致的性能并最大限度地利用您的代码,请手动展开。
像我这样的人在这些情况下所做的(性能为王)依赖于宏。
宏将在调试版本中“内联”(有用)并且可以使用宏参数进行模板化(到一定程度)。作为编译时常量的宏参数保证保持这种方式。
关于c - 在 C 中的内联函数中循环展开,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30802037/