Go编译原理之函数内联
本篇文章向大家介绍《Go编译原理之函数内联》,主要包括编译原理、函数内联,具有一定的参考价值,需要的朋友可以参考一下。
函数内联概述
我们知道每一个高级编程语言的函数调用,成本都是在与需要为它分配栈内存来存储参数、返回值、局部变量等等,Go的函数调用的成本在于参数与返回值栈复制、较小的栈寄存器开销以及函数序言部分的检查栈扩容(Go语言中的栈是可以动态扩容的,因为Go在分配栈内存不是逐渐增加的,而是一次性分配,这样是为了避免访问越界,它会一次性分配,当检查到分配的栈内存不够用时,它会扩容一个足够大的栈空间,并将原来栈中的内容拷贝过来)
下边写一段代码,通过Go的基准测试来测一下函数内联带来的效率提升
import "testing"
//go:noinline //禁用内联。如果要开启内联,将该行注释去掉即可
func max(a, b int) int {
if a > b {
return a
}
return b
}
var Result int
func BenchmarkMax(b *testing.B) {
var r int
for i:=0; i

在编译的过程中,Go的编译器其实会计算函数内联花费的成本,所以只有简单的函数,才会触发函数内联。在后边函数内联的源码实现中,我们可以看到下边这些情况不会被内联:
- 递归函数
- 函数前有如下注释的:
go:noinline、go:norace、go:nocheckptr、go:uintptrescapes等 - 没有函数体
- 函数声明的抽象语法树中节点数大于5000(我的Go版本是1.16.6)(也就是函数内部语句太多的情况,也不会被内联)
- 函数中包含闭包(
OCLOSURE)、range(ORANGE)、select(OSELECT)、go(OGO)、defer(ODEFER)、type(ODCLTYPE)、返回值是函数(ORETJMP)的,都不会内联
我们也可以构建或编译的时候,通过参数去控制它是否可以内联。如果希望程序中所有的函数都不执行内联操作
go build -gcflags="-l" xxx.go go tool compile -l xxx.go
同样我们在编译时,也可以查看哪些函数内联了,哪些函数没内联,以及原因是什么
go tool compile -m=2 xxx.go
看一个例子
package main
func test1(a, b int) int {
return a+b
}
func step(n int) int {
if n < 2 {
return n
}
return step(n-1) + step(n-2)
}
func main() {
test1(1, 2)
step(5)
}

可以看到test1这个函数是可以内联的,因为它的函数体很简单。step这个函数因为是递归函数,所以它不会进行内联
函数内联底层实现
这里边其实每一个函数调用链都很深,我这里不会一行一行的解释代码的含义,仅仅会将一些核心的方法拿出来介绍一下,感兴趣的小伙伴可以自己去调试一下(前边有发相关文章)(Go源码调试方法)
还是前边提到多次的Go编译入口文件,你可以在入口文件中找到这段代码
Go编译入口文件:src/cmd/compile/main.go -> gc.Main(archInit)
// Phase 5: Inlining
if Debug.l != 0 {
// 查找可以内联的函数
visitBottomUp(xtop, func(list []*Node, recursive bool) {
numfns := numNonClosures(list)
for _, n := range list {
if !recursive || numfns > 1 {
caninl(n)
} else {
......
}
inlcalls(n)
}
})
}
for _, n := range xtop {
if n.Op == ODCLFUNC {
devirtualize(n)
}
}
下边就看一下每个方法都在做哪些事情
visitBottomUp
该方法有两个参数:
xtop:前边已经见过它了,它存放的是每个声明语句的抽象语法树的根节点数组- 第二个参数是一个函数(该函数也有两个参数,一个是满足是函数类型声明的抽象语法树根节点数组,一个是bool值,true表示是递归函数,false表示不是递归函数)
进入到visitBottomUp方法中,你会发现它主要是遍历xtop,并对每个抽象语法树的根节点调用了visit这个方法(仅针对是函数类型声明的抽象语法树)
func visitBottomUp(list []*Node, analyze func(list []*Node, recursive bool)) {
var v bottomUpVisitor
v.analyze = analyze
v.nodeID = make(map[*Node]uint32)
for _, n := range list {
if n.Op == ODCLFUNC && !n.Func.IsHiddenClosure() { //是函数,并且不是闭包函数
v.visit(n)
}
}
}
而visit方法的核心是调用了inspectList方法,通过inspectList对抽象语法树按照深度优先搜索进行遍历,并将每一个节点作为inspectList方法的第二个参数(是一个函数)的参数,比如验证这个函数里边是否有递归调用等(具体就是下边的switch case)
func (v *bottomUpVisitor) visit(n *Node) uint32 {
if id := v.nodeID[n]; id > 0 {
// already visited
return id
}
......
v.stack = append(v.stack, n)
inspectList(n.Nbody, func(n *Node) bool {
switch n.Op {
case ONAME:
if n.Class() == PFUNC {
......
}
case ODOTMETH:
fn := asNode(n.Type.Nname())
......
}
case OCALLPART:
fn := asNode(callpartMethod(n).Type.Nname())
......
case OCLOSURE:
if m := v.visit(n.Func.Closure); m
后边通过调用visitBottomUp的第二个参数传递的方法,对抽象语法树进行内联的判断及内联操作,具体就是caninl和inlcalls这两个方法
caninl
该方法的作用就是验证是函数类型声明的抽象语法树是否可以内联
这个方法的实现很简单,首先是通过很多的if语句验证函数前边是否有像go:noinline等这种标记
func caninl(fn *Node) {
if fn.Op != ODCLFUNC {
Fatalf("caninl %v", fn)
}
if fn.Func.Nname == nil {
Fatalf("caninl no nname %+v", fn)
}
var reason string // reason, if any, that the function was not inlined
......
// If marked "go:noinline", don't inline
if fn.Func.Pragma&Noinline != 0 {
reason = "marked go:noinline"
return
}
// If marked "go:norace" and -race compilation, don't inline.
if flag_race && fn.Func.Pragma&Norace != 0 {
reason = "marked go:norace with -race compilation"
return
}
......
// If fn has no body (is defined outside of Go), cannot inline it.
if fn.Nbody.Len() == 0 {
reason = "no function body"
return
}
visitor := hairyVisitor{
budget: inlineMaxBudget,
extraCallCost: cc,
usedLocals: make(map[*Node]bool),
}
if visitor.visitList(fn.Nbody) {
reason = visitor.reason
return
}
if visitor.budget
这里边还有一个主要的方法就是visitList,它是用来验证函数里边是否有我们上边提到的go、select、range等等这些语句。对于满足内联条件的,它会将改写该函数声明抽闲语法树的内联字段(Inl)
inlcalls
该方法中就是具体的内联操作,比如将函数的参数和返回值转换为调用者中的声明语句等。里边的调用和实现都比较复杂,这里不粘代码了,大家可自行去看。函数内联的核心方法都在如下文件中
src/cmd/compile/internal/gc/inl.go
今天关于《Go编译原理之函数内联》的内容就介绍到这里了,是不是学起来一目了然!想要了解更多关于golang的内容请关注golang学习网公众号!
Golang 中的 unsafe.Pointer 和 uintptr详解
- 上一篇
- Golang 中的 unsafe.Pointer 和 uintptr详解
- 下一篇
- Go结合Gin导出Mysql数据到Excel表格
-
- 活力的大船
- 这篇技术文章真及时,太细致了,受益颇多,码起来,关注老哥了!希望老哥能多写Golang相关的文章。
- 2023-02-01 11:50:35
-
- 如意的跳跳糖
- 好细啊,mark,感谢师傅的这篇文章内容,我会继续支持!
- 2023-01-30 19:58:14
-
- 俊秀的睫毛
- 这篇技术贴出现的刚刚好,大佬加油!
- 2023-01-22 06:12:50
-
- 聪明的眼睛
- 写的不错,一直没懂这个问题,但其实工作中常常有遇到...不过今天到这,帮助很大,总算是懂了,感谢老哥分享文章!
- 2023-01-13 04:11:39
-
- 畅快的豆芽
- 这篇技术贴太及时了,好细啊,太给力了,码住,关注作者大大了!希望作者大大能多写Golang相关的文章。
- 2023-01-01 04:33:52
-
- Golang · Go教程 | 27分钟前 | go · 符号链接 · 路径处理 · filepath.EvalSymlinks ·
- Go filepath.EvalSymlinks 后为什么路径仍可能变化
- 227浏览 收藏
-
- Golang · Go教程 | 39分钟前 |
- Go filepath.Rel 计算相对路径时为什么会报错
- 299浏览 收藏
-
- Golang · Go教程 | 40分钟前 |
- Go filepath.Match 匹配 Windows 路径时怎么处理反斜杠
- 168浏览 收藏
-
- Golang · Go教程 | 2小时前 |
- Go os.ReadDir 返回顺序怎么与文件系统顺序区分
- 402浏览 收藏
-
- Golang · Go教程 | 2小时前 | go · 文件读取 · io.SectionReader · 字节区间 · 大文件 Go ReaderAt io.SectionReader 区间读取
- Go io.SectionReader 怎么读取大文件的指定字节区间
- 225浏览 收藏
-
- Golang · Go教程 | 2小时前 | 标准库 · go · 输入输出 · Go io.Reader io.MultiReader 输入流
- Go io.MultiReader 如何拼接多个输入流
- 434浏览 收藏
-
- Golang · Go教程 | 2小时前 |
- Go gzip 写入结束后怎么正确关闭并刷新尾部
- 397浏览 收藏
-
- Golang · Go教程 | 3小时前 |
- Go gzip Header.Name 怎么设置归档文件名
- 251浏览 收藏
-
- Golang · Go教程 | 3小时前 | go · gzip · 压缩流 · Go gzip compress/gzip Multistream
- Go gzip 多段压缩流怎么连续读取
- 428浏览 收藏
-
- Golang · Go教程 | 3小时前 | 文件处理 · Go教程 · 压缩归档 · Go archive/zip CreateHeader ZIP目录 FileHeader.Name
- Go archive/zip 写入目录条目时怎么避免路径混乱
- 493浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 41次使用
-
- SuperCLUE
- SuperCLUE是权威的中文大语言模型综合评测基准,涵盖语言理解、知识应用、AI Agent智能体及安全性等12项核心能力。通过多轮对话与客观测试,定期发布榜单与技术报告,为模型研发、优化及行业选型提供科学依据。
- 193次使用
-
- C-Eval
- 深入了解C-Eval中文评估套件,涵盖52个学科与4级难度。本文详解其功能特点、Zero-shot/Few-shot使用方法及代码示例,助您全面评测LLM中文理解与泛化能力。
- 129次使用
-
- AI Prompt Library
- 探索AI Prompt Library免费资源库,涵盖营销、写作及多场景AI提示词。兼容ChatGPT、Claude等工具,一键复制优化输出,提升工作效率。
- 60次使用
-
- Generrated
- Generrated汇集9300+张DALL·E生成图像及对应提示词,支持查看完整图集、对比DALL·E 2与3版本差异,是AI绘图新手学习Prompt设计与获取创作灵感的实用工具。
- 43次使用
-
- Go语言编译原理之源码调试
- 2022-12-31 458浏览
-
- Go语言编译原理之变量捕获
- 2022-12-31 144浏览
-
- gojson编译原理XJSON实现四则运算
- 2022-12-31 191浏览
-
- Go go/ast.Preorder 如何遍历语法树:迭代器消费、提前停止与节点类型判断
- 2026-08-28 239浏览
