8.4.2 内联判断
整个内联功能可以通过编译参数 -l
来禁止,对于某个特定函数,也可以通过编译命令 go:noinline
来禁止内联。除此之外,还有一些特定的情况下函数不能内联,例如函数体为空,或者函数包含一些特定的编译命令例如 go:cgo_unsafe_args
等。
除了这些明确的场景,一个函数能否内联取决于该函数的复杂度,前文中提到,函数调用对小函数影响更大,因此如何衡量函数的大小及复杂度,是编译器需要量化的事情。该逻辑由结构体 hairyVisitor
驱动:
编译器定义了一个“内联代价(Inline Cost)”来表达函数复杂度,内联代价与函数 AST 的节点数目正相关,每个子节点的 cost 为 1, 如果该节点还需要额外的操作,则还需要减掉对应的Cost, 例如函数调用的 Cost 是 57, 由常量 inlineExtraCallCost
定义;内联总代价小于 80 的函数可以内联,该数字被称为函数的“内联预算(Budget)”,由常量 inlineMaxBudget
定义。
计算内联代价的方法是 hairyVisitor.doNode()
, 该方法对函数的 AST(IR Tree) 进行深度优先(DFS)遍历,并从“内联预算(budget 字段)”中减掉函数体的各种“代价(cost)”,如果最终预算还有盈余,则函数可被内联。例如下列函数:
对于函数 B,语句 println("B")
总体的代价是 2, 因此整个函数的代价便是 4, 该函数没有用完内联预算,因此是可以内联的。而对于函数 A,其内联代价的计算如下:
Cost(A) = 2 + 2 + 57 = 61
2: 语句
println("A")
的代价2: 语句
C()
本身的代价57: 由于函数 C 无法内联,所以需要减去一个 extraCallCost, 默认为 57
A 仍然可以内联,但同时可以发现,如果 A 调用了两个以上不可内联的函数的话,那么他就会耗尽“预算”而无法内联了。
归总起来,判断函数能否内联的逻辑封装在函数 CanInline
中,精简代码之后其总体逻辑如下:
注意最后一部分逻辑,对于可以内联的函数, CanInline
会为该函数创建一个 ir.Inline
对象。
最后更新于