内联函数是否作为参数传递,真的在 C/C++ 中内联执行?
Are inline functions passed as argument, really executed inline in C/C++?
我有一个很长的(迭代次数)for 循环,我希望可以对其某些部分进行个性化设置。代码如下所示:
function expensive_loop( void (*do_true)(int), void (*do_false)(int)){
for(i=0; i<VeryLargeN; i++){
element=elements[i]
// long computation that produce a boolean condition
if (condition){
do_true(element);
}else{
do_false(element);
}
}
}
现在,问题是每次调用 do_true
和 do_false
时,由于堆栈的 push/pop 会产生开销,这会破坏代码的高性能.
为了解决这个问题,我可以简单地创建多个 expensive_loop
函数的副本,每个副本都有自己的 do_true 和 do_false 实现。这将使代码无法维护。
那么,人们如何制作迭代的内部部分,使其可以个性化,同时仍然保持高性能?
请注意,该函数接受 指向函数的指针,因此通过指针调用这些函数。如果 expensive_loop
的定义和那些函数可用并且没有违反编译器内联限制,优化器可能会通过函数指针内联这些调用。
另一种选择是使该算法成为接受可调用对象(函数指针、带有调用运算符的对象、lambda)的函数模板,就像标准算法一样。这样编译器可能会有更多的优化机会。例如:
template<class DoTrue, class DoFalse>
void expensive_loop(DoTrue do_true, DoFalse do_false) {
// Original function body here.
}
-Winline
g++
的编译器开关:
-Winline
Warn if a function can not be inlined and it was declared as inline. Even with this option, the compiler will not warn about failures to inline functions declared in system headers.
The compiler uses a variety of heuristics to determine whether or not to inline a function. For example, the compiler takes into account the size of the function being inlined and the the amount of inlining that has already been done in the current function. Therefore, seemingly insignificant changes in the source program can cause the warnings produced by -Winline
to appear or disappear.
当通过指针调用函数时,它可能不会警告函数未被内联。
问题是do_true
和do_false
中实际设置的函数地址,直到link时才解决,优化的机会不多。
如果您在代码中显式设置这两个函数(即函数本身不是来自外部库等),您可以使用 C++ 模板声明您的函数,以便编译器准确知道哪个你当时想调用的函数。
struct function_one {
void operator()( int element ) {
}
};
extern int elements[];
extern bool condition();
template < typename DoTrue, typename DoFalse >
void expensive_loop(){
DoTrue do_true;
DoFalse do_false;
for(int i=0; i<50; i++){
int element=elements[i];
// long computation that produce a boolean condition
if (condition()){
do_true(element); // call DoTrue's operator()
}else{
do_false(element); // call DoFalse's operator()
}
}
}
int main( int argc, char* argv[] ) {
expensive_loop<function_one,function_one>();
return 0;
}
编译器将为您指定的每个 DoTrue 和 DoFalse 类型组合实例化一个 expensive_loop
函数。如果您使用多个组合,它会增加可执行文件的大小,但它们中的每一个都应该达到您的预期。
对于我显示的示例,请注意函数是空的。
编译器只是剥离函数调用并离开循环:
main:
push rbx
mov ebx, 50
.L2:
call condition()
sub ebx, 1
jne .L2
xor eax, eax
pop rbx
ret
参见 https://godbolt.org/g/hV52Nn
中的示例
在您的示例中使用函数指针可能不会内联函数调用。这是在 expensive_loop
的程序中为 main
和 expensive_loop
生成的汇编器
// File A.cpp
void foo( int arg );
void bar( int arg );
extern bool condition();
extern int elements[];
void expensive_loop( void (*do_true)(int), void (*do_false)(int)){
for(int i=0; i<50; i++){
int element=elements[i];
// long computation that produce a boolean condition
if (condition()){
do_true(element);
}else{
do_false(element);
}
}
}
int main( int argc, char* argv[] ) {
expensive_loop( foo, bar );
return 0;
}
和参数传递的函数
// File B.cpp
#include <math.h>
int elements[50];
bool condition() {
return elements[0] == 1;
}
inline int foo( int arg ) {
return arg%3;
}
inline int bar( int arg ) {
return 1234%arg;
}
在不同的翻译单元中定义。
0000000000400620 <expensive_loop(void (*)(int), void (*)(int))>:
400620: 41 55 push %r13
400622: 49 89 fd mov %rdi,%r13
400625: 41 54 push %r12
400627: 49 89 f4 mov %rsi,%r12
40062a: 55 push %rbp
40062b: 53 push %rbx
40062c: bb 60 10 60 00 mov [=14=]x601060,%ebx
400631: 48 83 ec 08 sub [=14=]x8,%rsp
400635: eb 19 jmp 400650 <expensive_loop(void (*)(int), void (*)(int))+0x30>
400637: 66 0f 1f 84 00 00 00 nopw 0x0(%rax,%rax,1)
40063e: 00 00
400640: 48 83 c3 04 add [=14=]x4,%rbx
400644: 41 ff d5 callq *%r13
400647: 48 81 fb 28 11 60 00 cmp [=14=]x601128,%rbx
40064e: 74 1d je 40066d <expensive_loop(void (*)(int), void (*)(int))+0x4d>
400650: 8b 2b mov (%rbx),%ebp
400652: e8 79 ff ff ff callq 4005d0 <condition()>
400657: 84 c0 test %al,%al
400659: 89 ef mov %ebp,%edi
40065b: 75 e3 jne 400640 <expensive_loop(void (*)(int), void (*)(int))+0x20>
40065d: 48 83 c3 04 add [=14=]x4,%rbx
400661: 41 ff d4 callq *%r12
400664: 48 81 fb 28 11 60 00 cmp [=14=]x601128,%rbx
40066b: 75 e3 jne 400650 <expensive_loop(void (*)(int), void (*)(int))+0x30>
40066d: 48 83 c4 08 add [=14=]x8,%rsp
400671: 5b pop %rbx
400672: 5d pop %rbp
400673: 41 5c pop %r12
400675: 41 5d pop %r13
400677: c3 retq
400678: 0f 1f 84 00 00 00 00 nopl 0x0(%rax,%rax,1)
40067f: 00
即使使用 -O3
优化级别,您也可以看到如何仍然执行调用:
400644: 41 ff d5 callq *%r13
我有一个很长的(迭代次数)for 循环,我希望可以对其某些部分进行个性化设置。代码如下所示:
function expensive_loop( void (*do_true)(int), void (*do_false)(int)){
for(i=0; i<VeryLargeN; i++){
element=elements[i]
// long computation that produce a boolean condition
if (condition){
do_true(element);
}else{
do_false(element);
}
}
}
现在,问题是每次调用 do_true
和 do_false
时,由于堆栈的 push/pop 会产生开销,这会破坏代码的高性能.
为了解决这个问题,我可以简单地创建多个 expensive_loop
函数的副本,每个副本都有自己的 do_true 和 do_false 实现。这将使代码无法维护。
那么,人们如何制作迭代的内部部分,使其可以个性化,同时仍然保持高性能?
请注意,该函数接受 指向函数的指针,因此通过指针调用这些函数。如果 expensive_loop
的定义和那些函数可用并且没有违反编译器内联限制,优化器可能会通过函数指针内联这些调用。
另一种选择是使该算法成为接受可调用对象(函数指针、带有调用运算符的对象、lambda)的函数模板,就像标准算法一样。这样编译器可能会有更多的优化机会。例如:
template<class DoTrue, class DoFalse>
void expensive_loop(DoTrue do_true, DoFalse do_false) {
// Original function body here.
}
-Winline
g++
的编译器开关:
-Winline
Warn if a function can not be inlined and it was declared as inline. Even with this option, the compiler will not warn about failures to inline functions declared in system headers.
The compiler uses a variety of heuristics to determine whether or not to inline a function. For example, the compiler takes into account the size of the function being inlined and the the amount of inlining that has already been done in the current function. Therefore, seemingly insignificant changes in the source program can cause the warnings produced by
-Winline
to appear or disappear.
当通过指针调用函数时,它可能不会警告函数未被内联。
问题是do_true
和do_false
中实际设置的函数地址,直到link时才解决,优化的机会不多。
如果您在代码中显式设置这两个函数(即函数本身不是来自外部库等),您可以使用 C++ 模板声明您的函数,以便编译器准确知道哪个你当时想调用的函数。
struct function_one {
void operator()( int element ) {
}
};
extern int elements[];
extern bool condition();
template < typename DoTrue, typename DoFalse >
void expensive_loop(){
DoTrue do_true;
DoFalse do_false;
for(int i=0; i<50; i++){
int element=elements[i];
// long computation that produce a boolean condition
if (condition()){
do_true(element); // call DoTrue's operator()
}else{
do_false(element); // call DoFalse's operator()
}
}
}
int main( int argc, char* argv[] ) {
expensive_loop<function_one,function_one>();
return 0;
}
编译器将为您指定的每个 DoTrue 和 DoFalse 类型组合实例化一个 expensive_loop
函数。如果您使用多个组合,它会增加可执行文件的大小,但它们中的每一个都应该达到您的预期。
对于我显示的示例,请注意函数是空的。 编译器只是剥离函数调用并离开循环:
main:
push rbx
mov ebx, 50
.L2:
call condition()
sub ebx, 1
jne .L2
xor eax, eax
pop rbx
ret
参见 https://godbolt.org/g/hV52Nn
中的示例在您的示例中使用函数指针可能不会内联函数调用。这是在 expensive_loop
main
和 expensive_loop
生成的汇编器
// File A.cpp
void foo( int arg );
void bar( int arg );
extern bool condition();
extern int elements[];
void expensive_loop( void (*do_true)(int), void (*do_false)(int)){
for(int i=0; i<50; i++){
int element=elements[i];
// long computation that produce a boolean condition
if (condition()){
do_true(element);
}else{
do_false(element);
}
}
}
int main( int argc, char* argv[] ) {
expensive_loop( foo, bar );
return 0;
}
和参数传递的函数
// File B.cpp
#include <math.h>
int elements[50];
bool condition() {
return elements[0] == 1;
}
inline int foo( int arg ) {
return arg%3;
}
inline int bar( int arg ) {
return 1234%arg;
}
在不同的翻译单元中定义。
0000000000400620 <expensive_loop(void (*)(int), void (*)(int))>:
400620: 41 55 push %r13
400622: 49 89 fd mov %rdi,%r13
400625: 41 54 push %r12
400627: 49 89 f4 mov %rsi,%r12
40062a: 55 push %rbp
40062b: 53 push %rbx
40062c: bb 60 10 60 00 mov [=14=]x601060,%ebx
400631: 48 83 ec 08 sub [=14=]x8,%rsp
400635: eb 19 jmp 400650 <expensive_loop(void (*)(int), void (*)(int))+0x30>
400637: 66 0f 1f 84 00 00 00 nopw 0x0(%rax,%rax,1)
40063e: 00 00
400640: 48 83 c3 04 add [=14=]x4,%rbx
400644: 41 ff d5 callq *%r13
400647: 48 81 fb 28 11 60 00 cmp [=14=]x601128,%rbx
40064e: 74 1d je 40066d <expensive_loop(void (*)(int), void (*)(int))+0x4d>
400650: 8b 2b mov (%rbx),%ebp
400652: e8 79 ff ff ff callq 4005d0 <condition()>
400657: 84 c0 test %al,%al
400659: 89 ef mov %ebp,%edi
40065b: 75 e3 jne 400640 <expensive_loop(void (*)(int), void (*)(int))+0x20>
40065d: 48 83 c3 04 add [=14=]x4,%rbx
400661: 41 ff d4 callq *%r12
400664: 48 81 fb 28 11 60 00 cmp [=14=]x601128,%rbx
40066b: 75 e3 jne 400650 <expensive_loop(void (*)(int), void (*)(int))+0x30>
40066d: 48 83 c4 08 add [=14=]x8,%rsp
400671: 5b pop %rbx
400672: 5d pop %rbp
400673: 41 5c pop %r12
400675: 41 5d pop %r13
400677: c3 retq
400678: 0f 1f 84 00 00 00 00 nopl 0x0(%rax,%rax,1)
40067f: 00
即使使用 -O3
优化级别,您也可以看到如何仍然执行调用:
400644: 41 ff d5 callq *%r13