kod*_*dai 40 c++ optimization compilation inline function-call
现在,我知道这是因为没有调用函数的开销,但是调用函数的开销真的那么重(并且值得让它内联的膨胀)?
根据我的记忆,当一个函数被调用时,比如说f(x,y),x和y被压入堆栈,堆栈指针跳转到一个空块,然后开始执行.我知道这有点过于简单了,但我错过了什么吗?一些推送和跳转来调用一个函数,真的有那么多的开销吗?
如果我忘了什么,请告诉我,谢谢!
AnT*_*AnT 61
除了没有调用(因此没有相关费用,如调用之前的参数准备和调用之后的清理)之外,内联还有另一个显着优势.当函数体被内联时,它的主体可以在调用者的特定上下文中重新解释.这可能会立即允许编译器进一步减少和优化代码.
举一个简单的例子,这个函数
void foo(bool b) {
if (b) {
// something
}
else {
// something else
}
}
Run Code Online (Sandbox Code Playgroud)
如果被称为非内联函数,则需要实际分支
foo(true);
...
foo(false);
Run Code Online (Sandbox Code Playgroud)
但是,如果上面的调用是内联的,编译器将立即消除分支.本质上,在上面的情况下,内联允许编译器将函数参数解释为编译时常量(如果参数是编译时常量) - 这对于非内联函数通常是不可能的.
然而,它甚至不仅限于此.一般而言,启用内联的优化机会更为深远.再举一个例子,当函数体被内联到特定调用者的上下文中时,编译器在一般情况下将能够将调用代码中存在的已知别名相关关系传播到内联函数代码中,从而可以优化函数的代码更好.
同样,可能的示例很多,所有这些都源于内联调用沉浸在特定调用者的上下文中的基本事实,从而实现了各种上下文优化,这对于非内联调用是不可能的.通过内联,您基本上可以获得原始函数的许多单独版本,每个版本都针对每个特定的调用者上下文进行了单独定制和优化.显然,这样做的代价是代码膨胀的潜在危险,但如果使用得当,它可以提供显着的性能优势.
Che*_*Alf 26
"有几次推动和跳转来调用函数,真的有那么多开销吗?"
这取决于功能.
如果函数的主体只是一个机器代码指令,则调用和返回开销可以是很多百分之百.比如说,6次,500%的开销.然后,如果你的程序只包含大量的函数调用,没有内联,你的运行时间增加了500%.
但是,在另一个方向,内联可能会产生不利影响,例如,因为没有内联的代码会适合一页内存.
所以答案总是在优化方面,首先是MEASURE.
sbi*_*sbi 11
内联的经典候选者是一个访问者,就像std::vector<T>::size().
启用内联后,这只是从内存中获取变量,可能是任何体系结构上的单个指令."少数推动和跳跃"(加上回报)很容易多次.
除此之外,对优化器一次可见的代码越多,它的工作就越好.通过大量内联,它可以同时看到大量代码.这意味着它可以将值保存在CPU寄存器中,并完全避免昂贵的内存之旅.现在我们可能会有几个数量级的差异.
然后是theres 模板元编程.有时这会导致递归调用许多小函数,只是为了在递归结束时获取单个值.(考虑在具有几十个对象的元组中获取特定类型的第一个条目的值.)启用内联后,优化器可以直接访问该值(记住,可能在寄存器中),折叠了几十个函数调用访问CPU寄存器中的单个值.这可以将糟糕的性能变成一个漂亮而快速的程序.
将状态隐藏为对象中的私有数据(封装)会产生成本.内联是从一开始就是C++的一部分,以便最大限度地降低这些抽象成本.那时候,编译器在检测内联(并拒绝坏内容)的优秀候选者方面明显比现在更糟糕,因此手动内联导致了相当大的速度提升.
如今,编译器被认为比内联更加聪明.编译器能够自动内联函数,或者不标记为用户标注的内联函数inline,即使它们可以.有人说内联应该完全留给编译器,我们甚至不应该把功能标记为inline.但是,我还没有看到一个全面的研究表明手动这样做是否仍然值得.所以暂时,我会继续自己做,并让编译器覆盖它,如果它认为它可以做得更好.
让
int sum(const int &a,const int &b)
{
return a + b;
}
int a = sum(b,c);
Run Code Online (Sandbox Code Playgroud)
等于
int a = b + c
Run Code Online (Sandbox Code Playgroud)
没有跳跃 - 没有开销
考虑一个简单的函数,如:
int SimpleFunc (const int X, const int Y)
{
return (X + 3 * Y);
}
int main(int argc, char* argv[])
{
int Test = SimpleFunc(11, 12);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
这转换为以下代码(MSVC++ v6,debug):
10: int SimpleFunc (const int X, const int Y)
11: {
00401020 push ebp
00401021 mov ebp,esp
00401023 sub esp,40h
00401026 push ebx
00401027 push esi
00401028 push edi
00401029 lea edi,[ebp-40h]
0040102C mov ecx,10h
00401031 mov eax,0CCCCCCCCh
00401036 rep stos dword ptr [edi]
12: return (X + 3 * Y);
00401038 mov eax,dword ptr [ebp+0Ch]
0040103B imul eax,eax,3
0040103E mov ecx,dword ptr [ebp+8]
00401041 add eax,ecx
13: }
00401043 pop edi
00401044 pop esi
00401045 pop ebx
00401046 mov esp,ebp
00401048 pop ebp
00401049 ret
Run Code Online (Sandbox Code Playgroud)
您可以看到函数体只有4条指令,但只有15条指令用于函数开销,不包括另外3条用于调用函数本身的指令.如果所有指令都花费了相同的时间(它们没有),则80%的代码是函数开销.
对于像这样的平凡函数,函数开销代码很可能与主函数体本身一样长.当你有一个在深循环体中调用的琐碎函数数百万/数十亿次,那么函数调用开销就会变得很大.
与往常一样,关键是分析/测量以确定内联特定函数是否产生任何净性能增益.对于更"复杂"的功能而言,这些功能并非经常被称为"内联",因此内联的收益可能会非常小.