![Go语言底层原理剖析](https://wfqqreader-1252317822.image.myqcloud.com/cover/131/40795131/b_40795131.jpg)
1.8 函数内联
函数内联指将较小的函数直接组合进调用者的函数。这是现代编译器优化的一种核心技术。函数内联的优势在于,可以减少函数调用带来的开销。对于Go语言来说,函数调用的成本在于参数与返回值栈复制、较小的栈寄存器开销以及函数序言部分的检查栈扩容(Go语言中的栈是可以动态扩容的),详见第9章。同时,函数内联是其他编译器优化(例如无效代码消除)的基础。我们可以通过一段简单的程序衡量函数内联带来的效率提升[3],如下所示,使用bench对max函数调用进行测试。当我们在函数的注释前方加上//go:noinline时,代表当前函数是禁止进行函数内联优化的。取消该注释后,max函数将会对其进行内联优化。
![](https://epubservercos.yuewen.com/88BA42/21190707608528606/epubprivate/OEBPS/Images/41662_26_3.jpg?sign=1739685124-R3OcHXsZnUEusBRNAsMKSa4eq5Q6JIpU-0-e6f8de78bc86d0f32798ec993f601310)
![](https://epubservercos.yuewen.com/88BA42/21190707608528606/epubprivate/OEBPS/Images/41662_27_1.jpg?sign=1739685124-kONbbDRScyvYqWWeMwtxWZI62OClg6dn-0-c3dbfe100cbf872a9098be292fd56fe0)
通过下面的bench对比结果可以看出,在内联后,max函数的执行时间显著少于非内联函数调用花费的时间,这里的消耗主要来自函数调用增加的执行指令。
![](https://epubservercos.yuewen.com/88BA42/21190707608528606/epubprivate/OEBPS/Images/41662_27_2.jpg?sign=1739685124-6efYdh0vVNBjJ5TDE9KgYt6FbofeStax-0-2c053621c53014bc8c59d541fb914b89)
Go语言编译器会计算函数内联花费的成本,只有执行相对简单的函数时才会内联。函数内联的核心逻辑位于gc/inl.go中。当函数内部有for、range、go、select等语句时,该函数不会被内联,当函数执行过于复杂(例如太多的语句或者函数为递归函数)时,也不会执行内联。
![](https://epubservercos.yuewen.com/88BA42/21190707608528606/epubprivate/OEBPS/Images/41662_27_3.jpg?sign=1739685124-Xi0CD6Lvybi3tsfOo8Ls4v4jU3UfmJUX-0-d69d8d491c64a38a254cd31fe48d22d3)
另外,如果函数前的注释中有go:noinline标识,则该函数不会执行内联。如果希望程序中所有的函数都不执行内联操作,那么可以添加编译器选项“-l”。在之后的章节中会频繁地使用这种技巧进行调试。
![](https://epubservercos.yuewen.com/88BA42/21190707608528606/epubprivate/OEBPS/Images/41662_27_4.jpg?sign=1739685124-jDwooIPz9SF3Xqfah4oc46hWiLguVgpT-0-5babe58b7c03a1730bf67a5430ff468f)
在调试时,可以获取当前函数是否可以内联,以及不可以内联的原因。
![](https://epubservercos.yuewen.com/88BA42/21190707608528606/epubprivate/OEBPS/Images/41662_28_1.jpg?sign=1739685124-VAZa1NX56vVROIKK6eseK8dPybjjGPFj-0-80d2f38d635f4185664c4571c0ccc2fb)
在上面的代码中,当在编译时加入-m=2标志时,可以打印出函数的内联调试信息。可以看出,small函数可以被内联,而fib(斐波那契)函数为递归函数,不能被内联。
![](https://epubservercos.yuewen.com/88BA42/21190707608528606/epubprivate/OEBPS/Images/41662_28_2.jpg?sign=1739685124-AlmofiOFVUQNrnUpQFOmrDdrQgQw2zhz-0-c7afd4bd7c4dad839697b8093a1de620)
当函数可以被内联时,该函数将被纳入调用函数。如下所示,a:=b+f(1),其中,f函数可以被内联。
![](https://epubservercos.yuewen.com/88BA42/21190707608528606/epubprivate/OEBPS/Images/41662_28_3.jpg?sign=1739685124-alDL7oqVna9LW82d30fOz1SgTtroyIa2-0-b312b01270440eec45188aee0462e199)
函数参数与返回值在编译器内联阶段都将转换为声明语句,并通过goto语义跳转到调用者函数语句中,上述代码的转换形式如下,在后续编译器阶段还将对该内联结构做进一步优化。
![](https://epubservercos.yuewen.com/88BA42/21190707608528606/epubprivate/OEBPS/Images/41662_28_4.jpg?sign=1739685124-SGFVCIrFSqvGlYCqGITM6GkqxNnovkkw-0-896fea92377fc30d4c68bcaa62d00264)