我有一个关于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
谢谢!
答案 0 :(得分:3)
嗯,很明显,编译器不够'聪明'足以传播n
常量并展开for
循环。实际上它是安全的,因为arg->n
可以在实例化和使用之间改变。
为了在编译器代之间保持一致的性能并挤出代码的最大值,请手动展开。
像我这样的人在这些情况下做的事情(表现为王)依赖于宏。
宏将在调试版本中“内联”(有用),并且可以使用宏参数进行模板化(到某一点)。作为编译时常量的宏参数保证保持这种方式。
答案 1 :(得分:2)
速度更快,因为你的程序没有为变量分配内存。
如果您不必对未知值执行任何操作,则会将其视为#define constant 2
进行类型检查。它们只是在编译时添加的。
你能不能选择两个标签中的一个(我的意思是C或C ++),这让人感到困惑,因为语言对const
值的处理方式不同 - C将它们看作普通变量,它们的价值就是&#&# 39; t被改变,并且在C ++中,他们根据上下文分配了内存(如果你需要他们的地址,或者你需要在程序运行时计算它们,那么就会分配内存)。 / p>
来源:&#34;用C ++思考&#34;。没有确切的引用。