内联函数定义
在 wiki 中可以找到相关定义
在计算机科学中,内联函数(有时称作在线函数或编译时期展开函数)是一种编程语言结构,用来建议编译器对一些特殊函数进行内联扩展(有时称作在线扩展);也就是说建议编译器将指定的函数体插入并取代每一处调用该函数的地方(上下文),从而节省了每次调用函数带来的额外时间开支。但在选择使用内联函数时,必须在程序占用空间和程序执行效率之间进行权衡,因为过多的比较复杂的函数进行内联扩展将带来很大的存储资源开支。另外还需要特别注意的是对递归函数的内联扩展可能引起部分编译器的无穷编译。
在两个 Function A , B 中,A 调用 B, 把 B 设置为内联函数时,在编译时,B 的函数体会被展开,插入到 A 调用 B 的地方。编译后的程序中并没有找到 B 函数的符号。
在 C/C++ 中, 用 inline 来显示的说明函数是内联的。
go 中的内联函数
在 go 中没有 inline 关键字来设置内联函数。 但是 go 编译器会对小函数进行内联优化。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
package main import ( "fmt" ) func functionA() { fmt.Println("hello") } func main() { functionA(); } |
直接编译 go build -o main .
使用 readelf 查看函数符号
1 2 3 4 5 6 7 8 9 |
readelf -Ws main | grep main 598: 0000000000434d20 959 FUNC GLOBAL DEFAULT 1 runtime.main 985: 000000000045e480 80 FUNC GLOBAL DEFAULT 1 runtime.main.func1 986: 000000000045e4e0 60 FUNC GLOBAL DEFAULT 1 runtime.main.func2 1561: 0000000000497680 138 FUNC GLOBAL DEFAULT 1 main.main 1562: 0000000000535ec0 32 OBJECT GLOBAL DEFAULT 9 main..inittask 1766: 000000000054ab50 8 OBJECT GLOBAL DEFAULT 11 runtime.main_init_done 1767: 0000000000578210 1 OBJECT GLOBAL DEFAULT 12 runtime.mainStarted 2166: 00000000004d8868 8 OBJECT GLOBAL DEFAULT 2 runtime.mainPC |
发现并没有 functionA 符号。
使用 go tool objdump 来看下反编译后的代码
go tool objdump -s main main
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
TEXT main.main(SB) /root/projects/go/main.go main.go:13 0x497680 64488b0c25f8ffffff MOVQ FS:0xfffffff8, CX main.go:13 0x497689 483b6110 CMPQ 0x10(CX), SP main.go:13 0x49768d 7671 JBE 0x497700 main.go:13 0x49768f 4883ec58 SUBQ $0x58, SP main.go:13 0x497693 48896c2450 MOVQ BP, 0x50(SP) main.go:13 0x497698 488d6c2450 LEAQ 0x50(SP), BP main.go:14 0x49769d 0f57c0 XORPS X0, X0 main.go:9 0x4976a0 0f11442440 MOVUPS X0, 0x40(SP) main.go:9 0x4976a5 488d05d4b00000 LEAQ 0xb0d4(IP), AX main.go:9 0x4976ac 4889442440 MOVQ AX, 0x40(SP) main.go:9 0x4976b1 488d0518150400 LEAQ 0x41518(IP), AX main.go:9 0x4976b8 4889442448 MOVQ AX, 0x48(SP) print.go:274 0x4976bd 488b052c340b00 MOVQ os.Stdout(SB), AX print.go:274 0x4976c4 488d0ddd280400 LEAQ go.itab.*os.File,io.Writer(SB), CX print.go:274 0x4976cb 48890c24 MOVQ CX, 0(SP) print.go:274 0x4976cf 4889442408 MOVQ AX, 0x8(SP) print.go:274 0x4976d4 488d442440 LEAQ 0x40(SP), AX print.go:274 0x4976d9 4889442410 MOVQ AX, 0x10(SP) print.go:274 0x4976de 48c744241801000000 MOVQ $0x1, 0x18(SP) print.go:274 0x4976e7 48c744242001000000 MOVQ $0x1, 0x20(SP) print.go:274 0x4976f0 e88b9affff CALL fmt.Fprintln(SB) main.go:9 0x4976f5 488b6c2450 MOVQ 0x50(SP), BP main.go:9 0x4976fa 4883c458 ADDQ $0x58, SP main.go:9 0x4976fe c3 RET main.go:13 0x4976ff 90 NOPL main.go:13 0x497700 e8dbaefcff CALL runtime.morestack_noctxt(SB) main.go:13 0x497705 e976ffffff JMP main.main(SB) |
我们看到 main 函数里没有对 functionA 的调用, 而是把 functionA 直接展开,被内联到 main 函数中了。 所以,也就没有 functionA 符号了。
如果想看 go 编译器做了什么优化,可以使用 gcflags 进行查看
go build -gcflags –help
gcflags 传递的是对go 编译器的选项, 和 查看 go tool compile 是一样的
1 2 |
-m print optimization decisions -l disable inlining |
使用 -m 可以打印出优化选项。
go build -gcflags -m -o main .
1 2 3 4 5 6 7 8 9 10 11 |
go build -gcflags -m -o main . # demo ./main.go:8:6: can inline functionA ./main.go:9:13: inlining call to fmt.Println ./main.go:13:6: can inline main ./main.go:14:11: inlining call to functionA ./main.go:14:11: inlining call to fmt.Println ./main.go:9:14: "hello" escapes to heap ./main.go:9:13: []interface {}{...} does not escape ./main.go:14:11: "hello" escapes to heap ./main.go:14:11: []interface {}{...} does not escape |
可以看到有 inline 的优化。
使用 -l 可以禁用 inline .
1 |
go build -gcflags -l -o main . |
调用之后, 重新readelf 查看符号
1 2 3 4 5 6 7 8 9 10 |
readelf -Ws main | grep main 598: 0000000000434d20 959 FUNC GLOBAL DEFAULT 1 runtime.main 985: 000000000045e480 80 FUNC GLOBAL DEFAULT 1 runtime.main.func1 986: 000000000045e4e0 60 FUNC GLOBAL DEFAULT 1 runtime.main.func2 1562: 0000000000497720 110 FUNC GLOBAL DEFAULT 1 main.functionA 1563: 00000000004977a0 53 FUNC GLOBAL DEFAULT 1 main.main 1564: 0000000000535ec0 32 OBJECT GLOBAL DEFAULT 9 main..inittask 1768: 000000000054ab50 8 OBJECT GLOBAL DEFAULT 11 runtime.main_init_done 1769: 0000000000578210 1 OBJECT GLOBAL DEFAULT 12 runtime.mainStarted 2168: 00000000004d8848 8 OBJECT GLOBAL DEFAULT 2 runtime.mainPC |
可以看到有 functionA 的符号。