在 C 中的内联函数中循环展开
Loop unrolling in inlined functions in C
我有一个关于 C 编译器优化的问题,when/how 展开了内联函数中的循环。
我正在开发一个数字代码,它的功能类似于下面的示例。基本上,my_for()
会计算某种模板并调用 op()
为每个 i
对 my_type *arg
中的数据做一些事情。在这里,my_func()
包装 my_for()
,创建参数并将函数指针发送到 my_op()
...谁的工作是为每个 ( 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
.
这里的困惑是:如果我在 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
来真正向编译器发出这种 const-ness 信号,但它只是不想展开循环。
在我的数字代码中,这相当于大约 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
谢谢!
速度更快,因为您的程序没有为变量分配内存。
如果您不必对未知值执行任何操作,它们将被视为 #define constant 2
并进行类型检查。它们只是在编译时添加的。
请您选择两个标签之一(我的意思是 C 或 C++),这很令人困惑,因为语言对待 const
值的方式不同 - C 将它们视为普通变量,而值不能改变了,在 C++ 中,它们根据上下文分配或不分配内存(如果您需要它们的地址,或者如果您需要在程序 运行 时计算它们,则分配内存)。
来源:"Thinking in C++"。没有确切的报价。
嗯,显然编译器 'smart' 不足以传播 n
常量和展开 for
循环。实际上它是安全的,因为 arg->n
可以在实例化和使用之间改变。
为了在各代编译器中获得一致的性能并最大限度地利用您的代码,请手动展开。
像我这样的人在这些情况下(性能为王)所做的就是依赖宏。
宏将 'inline' 在调试版本中(有用)并且可以使用宏参数进行模板化(到一定程度)。作为编译时常量的宏参数保证保持这种方式。
我有一个关于 C 编译器优化的问题,when/how 展开了内联函数中的循环。
我正在开发一个数字代码,它的功能类似于下面的示例。基本上,my_for()
会计算某种模板并调用 op()
为每个 i
对 my_type *arg
中的数据做一些事情。在这里,my_func()
包装 my_for()
,创建参数并将函数指针发送到 my_op()
...谁的工作是为每个 ( 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
.
这里的困惑是:如果我在 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
来真正向编译器发出这种 const-ness 信号,但它只是不想展开循环。
在我的数字代码中,这相当于大约 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
谢谢!
速度更快,因为您的程序没有为变量分配内存。
如果您不必对未知值执行任何操作,它们将被视为 #define constant 2
并进行类型检查。它们只是在编译时添加的。
请您选择两个标签之一(我的意思是 C 或 C++),这很令人困惑,因为语言对待 const
值的方式不同 - C 将它们视为普通变量,而值不能改变了,在 C++ 中,它们根据上下文分配或不分配内存(如果您需要它们的地址,或者如果您需要在程序 运行 时计算它们,则分配内存)。
来源:"Thinking in C++"。没有确切的报价。
嗯,显然编译器 'smart' 不足以传播 n
常量和展开 for
循环。实际上它是安全的,因为 arg->n
可以在实例化和使用之间改变。
为了在各代编译器中获得一致的性能并最大限度地利用您的代码,请手动展开。
像我这样的人在这些情况下(性能为王)所做的就是依赖宏。
宏将 'inline' 在调试版本中(有用)并且可以使用宏参数进行模板化(到一定程度)。作为编译时常量的宏参数保证保持这种方式。