Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Check inline expansion with Go

Tsuji Daishiro
December 04, 2019

Check inline expansion with Go

Tsuji Daishiro

December 04, 2019
Tweet

More Decks by Tsuji Daishiro

Other Decks in Technology

Transcript

  1. 関数のインライン展開とは? ✓ コンパイラのコード最適化テクのひとつ ✓ 関数呼び出しのコードを呼び出し元のコードに展開 ✓ 関数呼び出しのオーバヘッドを削減し、高速化 5 package main

    func main() { a, b := 10, 20 res := add(a, b) println(res) } func add(a, b int) int { return a + b } package main func main() { // a, b := 10, 20 // res := a + b res := 30 //インライン展開 +定数畳み込み println(res) } func add(a, b int) int { return a + b }
  2. インライン展開する関数としない関数 8 package hello //go:noinline func HelloNoInline() interface{} { return

    struct{}{} } func Hello() interface{} { return struct{}{} } hello.go pragmaで明示的にインライン展開を抑制 ✓ 関数自体は空のstructをreturnする何もしない関数
  3. それぞれビルドしてアセンブリを出力 9 package main func main() { hello.HelloNoInline() } main.go

    go build main.go go tool objdump main.exe > dump_noinline.txt インライン展開しない package main func main() { hello.Hello() } main.go go build main.go go tool objdump main.exe > dump.txt インライン展開する
  4. インライン展開しない場合 10 TEXT github.com/d-tsuji/go-sandbox/opt/hello.HelloNoInline(SB) C:/Users/tsuji/go/src/github.com/d-tsuji/go-sandbox/opt/hello/hello.go hello.go:5 0x451e30 488d0549ec0000 LEAQ runtime.rodata+60032(SB),

    AX hello.go:5 0x451e37 4889442408 MOVQ AX, 0x8(SP) hello.go:5 0x451e3c 488d058d6d0800 LEAQ runtime.zerobase(SB), AX hello.go:5 0x451e43 4889442410 MOVQ AX, 0x10(SP) hello.go:5 0x451e48 c3 RET :-1 0x451e49 cc INT $0x3 :-1 0x451e4a cc INT $0x3 :-1 0x451e4b cc INT $0x3 :-1 0x451e4c cc INT $0x3 :-1 0x451e4d cc INT $0x3 :-1 0x451e4e cc INT $0x3 :-1 0x451e4f cc INT $0x3 TEXT main.main(SB) C:/Users/tsuji/go/src/github.com/d-tsuji/go-sandbox/opt/hello/cmd/main.go main.go:5 0x451e50 65488b0c2528000000 MOVQ GS:0x28, CX main.go:5 0x451e59 488b8900000000 MOVQ 0(CX), CX main.go:5 0x451e60 483b6110 CMPQ 0x10(CX), SP main.go:5 0x451e64 761d JBE 0x451e83 main.go:5 0x451e66 4883ec18 SUBQ $0x18, SP main.go:5 0x451e6a 48896c2410 MOVQ BP, 0x10(SP) main.go:5 0x451e6f 488d6c2410 LEAQ 0x10(SP), BP main.go:6 0x451e74 e8b7ffffff CALL github.com/d-tsuji/go-sandbox/opt/hello.HelloNoInline(SB) main.go:7 0x451e79 488b6c2410 MOVQ 0x10(SP), BP main.go:7 0x451e7e 4883c418 ADDQ $0x18, SP main.go:7 0x451e82 c3 RET main.go:5 0x451e83 e8487effff CALL runtime.morestack_noctxt(SB) main.go:5 0x451e88 ebc6 JMP main.main(SB) main.goからHelloNoInlineが呼び出されている
  5. インライン展開する場合 11 TEXT main.main(SB) C:/Users/tsuji/go/src/github.com/d-tsuji/go-sandbox/opt/hello/cmd/main.go main.go:5 0x451e30 c3 RET :0

    0x451e31 cc INT $0x3 :0 0x451e32 cc INT $0x3 :0 0x451e33 cc INT $0x3 :0 0x451e34 cc INT $0x3 :0 0x451e35 cc INT $0x3 :0 0x451e36 cc INT $0x3 :0 0x451e37 cc INT $0x3 :0 0x451e38 cc INT $0x3 :0 0x451e39 cc INT $0x3 :0 0x451e3a cc INT $0x3 :0 0x451e3b cc INT $0x3 :0 0x451e3c cc INT $0x3 :0 0x451e3d cc INT $0x3 :0 0x451e3e cc INT $0x3 :0 0x451e3f cc INT $0x3 main.goからHelloの呼び出しがなくなっている
  6. ベンチマークを取って関数呼び出しのコストを確認 14 package hello import "testing" func BenchmarkHello(b *testing.B) {

    for i := 0; i < b.N; i++ { Hello() } } func BenchmarkHelloNoInline(b *testing.B) { for i := 0; i < b.N; i++ { HelloNoInline() } } test_hello.go ✓ さきほどの関数を用いてベンチマークをとる
  7. ベンチマーク結果 15 go test -bench . –benchmem goos: windows goarch:

    amd64 pkg: github.com/d-tsuji/go-sandbox/opt/hello BenchmarkHello-8 2000000000 0.34 ns/op 0 B/op 0 allocs/op BenchmarkHelloNoInline-8 2000000000 1.51 ns/op 0 B/op 0 allocs/op PASS ok github.com/d-tsuji/go-sandbox/opt/hello 4.295s ✓ 関数呼び出しのコストだけ考えると5倍程度の違い(極端な例ですが) ✓ 関数自体のコストが小さい場合は、関数呼び出しのオーバヘッドが無視できない場合も
  8. ビルド時に最適化の結果を確認 17 ✓ 今回は実行ファイルのアセンブリから確認したかったためobjdumpを用いた ✓ ビルド時に最適化の結果を出力できる(インライン展開以外の最適化結果も出力) ✓ go build –gcflags

    “-m” main.go ✓ go build -gcflags “-m -m” main.go go build -gcflags "-m -m" main.go # command-line-arguments .¥main.go:5:6: can inline main as: func() { hello.Hello() } .¥main.go:6:13: inlining call to hello.Hello func() interface {} { return (interface {})(struct {} literal) } .¥main.go:6:13: main (interface {})(struct {} literal) does not escape 今回のインライン展開する実装例の場合だと、以下のような結果が得られる
  9. 関数がインライン展開される条件 18 ✓ Compiler And Runtime Optimizations ✓ https://github.com/golang/go/wiki/CompilerOptimizations ✓

    関数に含まれる expressions (式?)が40個未満 ✓ 関数呼び出し・ループ・クロージャー・panic・recover・select・switch といった複雑なものを含ま ない Only short and simple functions are inlined. To be inlined a function must contain less than ~40 expressions and does not contain complex things like function calls, loops, labels, closures, panic's, recover's, select's, switch'es, etc expressionsの解釈がわからず…