前言
asongGo
什么是内联函数
CCinlineinline
示例:
#include <stdio.h>
inline char* chooseParity(int a)
{
return (i % 2 > 0) ? "奇" : "偶";
}
int main()
{
int i = 0;
for (i=1; i < 100; i++)
{
printf("i:%d 奇偶性:%s /n", i, chooseParity(i));
}
}
复制代码
char* chooseParity(int a)inline
int i = 0;
for (i=1; i < 100; i++)
{
printf("i:%d 奇偶性:%s /n", i, (i % 2 > 0) ? "奇" : "偶");
}
复制代码
这样就避免了频繁调用函数对栈内存重复开辟所带来的消耗,我们都知道一些函数被频繁调用,会不断地有函数入栈,即函数栈,会造成栈空间或栈内存的大量消耗,内联函数的出现节省了每次调用函数带来的额外时间开支。但并不是所有场景都可以使用内联函数的,必须在程序占用空间和程序执行效率之间进行权衡,因为过多的比较复杂的函数进行内联扩展将带来很大的存储资源开支。
CinlineCAs-ifinlineGo//go:noinline
示例:
//go:noinline
func maxValue(a,b int) int {
if a > b{
return a
}
return b
}
复制代码
内联函数优化带来的性能提升
接下来,我们来写一个简单的例子看一看内联函数与非内联函数的差异。
//go:noinline
func AddNoinline(x,y,z int) int {
return x+y+z
}
func AddInline(x,y,z int) int {
return x+y+z
}
func BenchmarkAddNoinline(b *testing.B) {
x,y,z :=1,2,3
b.ResetTimer()
for i:=0;i<b.N;i++{
AddInline(x,y,z)
}
}
func BenchmarkAddInline(b *testing.B) {
x,y,z :=1,2,3
b.ResetTimer()
for i:=0;i<b.N;i++{
AddNoinline(x,y,z)
}
}
复制代码
运行结果:
goos: darwin
goarch: amd64
pkg: asong.cloud/Golang_Dream/code_demo/inline
BenchmarkAddNoinline-16 722205944 1.55 ns/op
BenchmarkAddInline-16 1000000000 0.237 ns/op
PASS
ok asong.cloud/Golang_Dream/code_demo/inline 3.200s
复制代码
从运行结果我们可以看出内联函数的处理速度还是略快于非内联函数,因为我这个例子比较简单,所以差异还不是特别明显。
查看编译器做了什么优化
--gcflags=-m --gcflags="-m -m"
示例:
func main(){
s := []int{10,12,3,14}
fmt.Println(GetMaxValue(s))
}
func GetMaxValue(s []int) int {
max :=0
for i:=0;i<len(s);i++{
max = maxValue(s[i],max)
}
return max
}
func maxValue(a,b int) int {
if a > b{
return a
}
return b
}
复制代码
go build --gcflags="-m -m" ./test.go
# command-line-arguments
./test.go:20:6: can inline maxValue with cost 8 as: func(int, int) int { if a > b { return a }; return b }
./test.go:12:6: cannot inline GetMaxValue: unhandled op FOR
./test.go:15:17: inlining call to maxValue func(int, int) int { if a > b { return a }; return b }
./test.go:7:6: cannot inline main: function too complex: cost 145 exceeds budget 80
./test.go:9:13: inlining call to fmt.Println func(...interface {}) (int, error) { var fmt..autotmp_3 int; fmt..autotmp_3 = <N>; var fmt..autotmp_4 error; fmt..autotmp_4 = <N>; fmt..autotmp_3, fmt..autotmp_4 = fmt.Fprintln(io.Writer(os.Stdout), fmt.a...); return fmt..autotmp_3, fmt..autotmp_4 }
./test.go:12:18: s does not escape
./test.go:9:25: GetMaxValue(s) escapes to heap:
./test.go:9:25: flow: ~arg0 = &{storage for GetMaxValue(s)}:
./test.go:9:25: from GetMaxValue(s) (spill) at ./test.go:9:25
./test.go:9:25: from ~arg0 = <N> (assign-pair) at ./test.go:9:13
./test.go:9:25: flow: {storage for []interface {} literal} = ~arg0:
./test.go:9:25: from []interface {} literal (slice-literal-element) at ./test.go:9:13
./test.go:9:25: flow: fmt.a = &{storage for []interface {} literal}:
./test.go:9:25: from []interface {} literal (spill) at ./test.go:9:13
./test.go:9:25: from fmt.a = []interface {} literal (assign) at ./test.go:9:13
./test.go:9:25: flow: {heap} = *fmt.a:
./test.go:9:25: from fmt.Fprintln(io.Writer(os.Stdout), fmt.a...) (call parameter) at ./test.go:9:13
./test.go:8:12: []int literal does not escape
./test.go:9:25: GetMaxValue(s) escapes to heap
./test.go:9:13: []interface {} literal does not escape
<autogenerated>:1: .this does not escape
复制代码
maxValueGetMaxValuemaxValueGetMaxValueFORGofmt.Println
Go
/src/cmd/compile/internal/gc/inl.go
inlineMaxBudget = 80
func (v *hairyVisitor) visit(n *Node) bool {
if n == nil {
return false
}
switch n.Op {
.... //省略部分代码
case OCLOSURE,
OCALLPART,
ORANGE,
OFOR,
OFORUNTIL,
OSELECT,
OTYPESW,
OGO,
ODEFER,
ODCLTYPE, // can't print yet
OBREAK,
ORETJMP:
v.reason = "unhandled op " + n.Op.String()
return true
.... //省略部分代码
}
return v.visit(n.Left) || v.visit(n.Right) ||
v.visitList(n.List) || v.visitList(n.Rlist) ||
v.visitList(n.Ninit) || v.visitList(n.Nbody)
}
复制代码
从这里可以看出一个规则:
selectfordefergogoroutineAST
1300+
内联函数带来的问题
panicpanicGo
Go-gcflags="-d pctab=pctoinline"
func main(){
s := []int{90,100,24,18}
Sum(s)
}
func Sum(s []int) int {
sum :=0
for i:=0;i<len(s);i++{
sum = add(sum,s[i])
}
return sum
}
func add(x,y int) int{
panic("panic")
return x+y
}
复制代码
go build -gcflags="-d pctab=pctoinline" ./test1.go
-- inlining tree for "".Sum:
0 | -1 | "".add (/Users/go/src/asong.cloud/Golang_Dream/code_demo/inline/test1.go:11:12) pc=39
--
复制代码
inlining tree.Sum.add
总结
内联函数对于程序的提升是很重要的,函数调用是有开销的,比如:创建新的堆栈帧、保存和恢复寄存器等,所以内联函数的优化可以有效避免一些不必要的开销,你学会了吗?宝贝!
github
asong
创建了一个Golang学习交流群,欢迎各位大佬们踊跃入群,我们一起学习交流。入群方式:关注公众号获取。更多学习资料请到公众号领取。
欢迎关注公众号:Golang梦工厂
推荐往期文章: