- Go语言底层原理剖析
- 郑建勋
- 771字
- 2021-10-15 18:08:59
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=1739576476-IsH2E3Hlgqzdix6cjdPeP5nHhUiRdve6-0-648468aea86e2e0606122b5bd75ee1af)
![](https://epubservercos.yuewen.com/88BA42/21190707608528606/epubprivate/OEBPS/Images/41662_27_1.jpg?sign=1739576476-MXwspq9kOhnyAaM6ae97dWc6IjrfZpTH-0-ab3246395936ec0f20a6aed1de3a233d)
通过下面的bench对比结果可以看出,在内联后,max函数的执行时间显著少于非内联函数调用花费的时间,这里的消耗主要来自函数调用增加的执行指令。
![](https://epubservercos.yuewen.com/88BA42/21190707608528606/epubprivate/OEBPS/Images/41662_27_2.jpg?sign=1739576476-4JqaElVOMSLHmmW0tc1wiBcRN1SSoanG-0-7ac60299579d8d57e74b82f2aa78642b)
Go语言编译器会计算函数内联花费的成本,只有执行相对简单的函数时才会内联。函数内联的核心逻辑位于gc/inl.go中。当函数内部有for、range、go、select等语句时,该函数不会被内联,当函数执行过于复杂(例如太多的语句或者函数为递归函数)时,也不会执行内联。
![](https://epubservercos.yuewen.com/88BA42/21190707608528606/epubprivate/OEBPS/Images/41662_27_3.jpg?sign=1739576476-7lgoC3X9qo03kRHVkQNNj3KFGw4ZXRs4-0-ac76c3e11fbb9a832b575791f033ff3f)
另外,如果函数前的注释中有go:noinline标识,则该函数不会执行内联。如果希望程序中所有的函数都不执行内联操作,那么可以添加编译器选项“-l”。在之后的章节中会频繁地使用这种技巧进行调试。
![](https://epubservercos.yuewen.com/88BA42/21190707608528606/epubprivate/OEBPS/Images/41662_27_4.jpg?sign=1739576476-J7ldHpQrNr0ltAD0LPFT7rmOFpuRUazP-0-5248f040913e56817e09a1b74cd1f21a)
在调试时,可以获取当前函数是否可以内联,以及不可以内联的原因。
![](https://epubservercos.yuewen.com/88BA42/21190707608528606/epubprivate/OEBPS/Images/41662_28_1.jpg?sign=1739576476-GckEQqzHa1bbW7EO8l69STmCrLCgjQ2Z-0-f04fc47f2851eb92f9778c074769f659)
在上面的代码中,当在编译时加入-m=2标志时,可以打印出函数的内联调试信息。可以看出,small函数可以被内联,而fib(斐波那契)函数为递归函数,不能被内联。
![](https://epubservercos.yuewen.com/88BA42/21190707608528606/epubprivate/OEBPS/Images/41662_28_2.jpg?sign=1739576476-mx8ZwtNzcTFM05HtniKEFfYiUcQiIzuL-0-5b9ac039801c2bbe6f7bd06d5b18085b)
当函数可以被内联时,该函数将被纳入调用函数。如下所示,a:=b+f(1),其中,f函数可以被内联。
![](https://epubservercos.yuewen.com/88BA42/21190707608528606/epubprivate/OEBPS/Images/41662_28_3.jpg?sign=1739576476-Tj1MjA9xNsBe5mOcPV9Fv1y9g9v6VW1l-0-da5453aa9d92add85770ff3983acc89e)
函数参数与返回值在编译器内联阶段都将转换为声明语句,并通过goto语义跳转到调用者函数语句中,上述代码的转换形式如下,在后续编译器阶段还将对该内联结构做进一步优化。
![](https://epubservercos.yuewen.com/88BA42/21190707608528606/epubprivate/OEBPS/Images/41662_28_4.jpg?sign=1739576476-1UaZupRPJX6WRZn1RhcbFLpuKmTO6who-0-5ecd356e512e9dd8d75a1976b3d2061f)