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

Goのメモリ管理 / Memory management in Go

Goのメモリ管理 / Memory management in Go

ymotongpoo

June 02, 2023
Tweet

More Decks by ymotongpoo

Other Decks in Programming

Transcript

  1. Confidential & Proprietary Goのメモリ管理 Go Conference 2023 Track B 16:00-16:20

    June 2nd, 2023 山口能迪 (@ymotongpoo) bit.ly/20230602-gocon
  2. TLB MMU CPU DRAM プロセス (仮想メモリ) A A B B

    ディスク B C C 0x1000 0x8000 0x2000 0x7000 メモリマップトファイル ページフォルト 0x3000 スワップ ページ (4KB) ページ テーブル
  3. 0x0 0xFFFFFFFFFFFFFFFF プログラムコード 初期化データ .bss ヒープ スタック スタック スタック 仮想メモリ

    NOTE: プログラムからは仮想メモリしか触れない 共有ライブラリ 8MB
  4. プログラム コード 初期化データ .bss ヒープ スタック スタック スタック OSとの対応 共有ライブラリ

    mheap arena … mcentral … 8MB 2KB プロセス Goランタイム ほぼヒープが問題
  5. mheap P1 (論理プロセッサー) Go内部のメモリ管理概要 mcache … arena mcentral mcentral mcentral

    …… … … span (non empty) span (empty) … stack P2 stack Pn stack mcache … mspan … mspan mspan
  6. mspan と mcentral mspan (non empty) mspan (empty) • mspan

    は事前にさまざまなサイズクラスでアロケートした オブジェクト保存⽤のページを管理している • サイズクラスは 8B 〜 32KB のサイズのオブジェクトを合計 で32KBのサイズになる個数で保持している arena mcentral mcentral mcentral …… … … … mspan … mspan mspan
  7. src/runtime/sizeclasses.go // class bytes/obj bytes/span objects tail waste max waste

    min align // 1 8 8192 1024 0 87.50% 8 // 2 16 8192 512 0 43.75% 16 // 3 24 8192 341 8 29.24% 8 // 4 32 8192 256 0 21.88% 32 // 5 48 8192 170 32 31.52% 16 // 6 64 8192 128 0 23.44% 64 // 7 80 8192 102 32 19.07% 16 // 8 96 8192 85 32 15.95% 32 // 9 112 8192 73 16 13.56% 16 // 10 128 8192 64 0 11.72% 128 // 11 144 8192 56 128 11.82% 16 // 12 160 8192 51 32 9.73% 32 // 13 176 8192 46 96 9.59% 16 // 14 192 8192 42 128 9.25% 64 // 15 208 8192 39 80 8.12% 16 // 16 224 8192 36 128 8.15% 32 // 17 240 8192 34 32 6.62% 16 // 18 256 8192 32 0 5.86% 256 // 19 288 8192 28 128 12.16% 32 // 20 320 8192 25 192 11.80% 64 // 21 352 8192 23 96 9.88% 32 // 22 384 8192 21 128 9.51% 128 // 23 416 8192 19 288 10.71% 32
  8. スタックとヒープ スタック • レキシカルスコープ内 ◦ ローカル変数 ◦ 関数の引数と戻り値 • ポインターでない

    • LIFO ヒープ • レキシカルスコープ外 ◦ グローバル変数 ◦ 巨⼤なデータ • ポインター • ライフサイクルが予測不能 c.f. エスケープ解析
  9. スタックとヒープの実⽤上の特徴 スタック • 割当と解放が速い ◦ コンパイル時に決まる • ⼀時的なもの • サイズが⼩さい

    ヒープ • 割当と解放が遅い ◦ GCに頼らないといけない • ライフサイクルが予測不能 • サイズが⼤きい スタックで良いならスタックを使うようにする
  10. 問題: ヒープ or スタック? 5 func main() { 6 for

    i := 0; i < 100; i++ { 7 s := NewRectangle(i, 2*i) 8 fmt.Println(s.Area()) 9 } 10 } … 17 func NewRectangle(w, h int) Rectangle { 18 return Rectangle{ 19 Width: w, 20 Height: h, 21 } 22 } 23 24 func (r *Rectangle) Area() int { 25 return r.Width * r.Height 26 }
  11. go build gcflags -m $ go build -gcflags -m main.go

    # command-line-arguments ./main.go:17:6: can inline NewRectangle ./main.go:24:6: can inline (*Rectangle).Area ./main.go:7:20: inlining call to NewRectangle ./main.go:8:21: inlining call to (*Rectangle).Area ./main.go:8:14: inlining call to fmt.Println ./main.go:8:14: ... argument does not escape ./main.go:8:21: ~R0 escapes to heap ./main.go:24:7: r does not escape 最適化によりインライン展開している スタックにあります
  12. 例: ポインターを返してみる 5 func main() { 6 for i :=

    0; i < 100; i++ { 7 s := NewRectangle(i, 2*i) 8 fmt.Println(s.Area()) 9 } 10 } … 17 func NewRectangle(w, h int) *Rectangle { 18 return &Rectangle{ 19 Width: w, 20 Height: h, 21 } 22 } 23 24 func (r *Rectangle) Area() int { 25 return r.Width * r.Height 26 } ポインター型に変えてみる
  13. 例: ポインターを返してみる $ go build -gcflags "-m" main.go # command-line-arguments

    ./main.go:17:6: can inline NewRectangle ./main.go:24:6: can inline (*Rectangle).Area ./main.go:7:20: inlining call to NewRectangle ./main.go:8:21: inlining call to (*Rectangle).Area ./main.go:8:14: inlining call to fmt.Println ./main.go:7:20: &Rectangle{...} does not escape ./main.go:8:14: ... argument does not escape ./main.go:8:21: ~R0 escapes to heap ./main.go:18:9: &Rectangle{...} escapes to heap ./main.go:24:7: r does not escape ※この例では実際にはインラインされているので影響はない 関数の戻り値はヒープ に渡されます インライン展開されたもの はスタックにあります
  14. プリミティブ型のサイズ bool 1 byte int8 1 byte int16 2 byte

    int32 4 byte int64 8 byte uint8 1 byte uint16 2 byte uint32 4 byte uint64 8 byte float32 4 byte float64 8 byte complex64 8 byte complex128 16 byte byte (=uint8) 1 byte rune (=int32) 4 byte uintptr 8 byte str len int unsafe.Pointer ⽂字列 (string)
  15. コピーコスト type Rectangle struct { Width int // 8 byte

    Height int // 8 byte } var r *Rectangle // 8 byte https://go.dev/play/p/gSisu8NMYUH 16 byte データのサイズと実際にメモリに確保される場所とのトレードオフ
  16. 構造体 type Rectangle struct { Width uint32 Height uint16 }

    https://go.dev/play/p/0PrgLF9ChCK 6 byte?
  17. メモリアラインメント type Rectangle struct { Width uint32 Height uint16 }

    https://go.dev/play/p/RLBp2N_4tEo 6 byte? 8 byte パディング ランタイムがメモリにアクセスする単位がある
  18. type Rectangle struct { Width uint32 Height uint16 Init bool

    } メモリアラインメント type Rectangle struct { Init bool Width uint32 Height uint16 } https://go.dev/play/p/qkQeVPC3cjA
  19. go tool objdump $ go tool objdump -s main.main main

    TEXT main.main(SB) /Users/yoshifumi/personal/tmp/main.go main.go:20 0x10008ce00 f9400b90 MOVD 16(R28), R16 main.go:20 0x10008ce04 d10143f1 SUB $80, RSP, R17 main.go:20 0x10008ce08 eb10023f CMP R16, R17 main.go:20 0x10008ce0c 54000ce9 BLS 103(PC) main.go:20 0x10008ce10 f8130ffe MOVD.W R30, -208(RSP) main.go:20 0x10008ce14 f81f83fd MOVD R29, -8(RSP) main.go:20 0x10008ce18 d10023fd SUB $8, RSP, R29 main.go:24 0x10008ce1c a905ffff STP (ZR, ZR), 88(RSP) main.go:24 0x10008ce20 f0000147 ADRP 176128(PC), R7 main.go:24 0x10008ce24 913f00e7 ADD $4032, R7, R7 main.go:24 0x10008ce28 f9002fe7 MOVD R7, 88(RSP) main.go:24 0x10008ce2c 90000108 ADRP 131072(PC), R8 main.go:24 0x10008ce30 910c0108 ADD $768, R8, R8 main.go:24 0x10008ce34 f90033e8 MOVD R8, 96(RSP) https://pkg.go.dev/cmd/internal/obj/[email protected]