Slide 1

Slide 1 text

Go 1.26でのsliceのメモリアロケーション最適化 @mazrean Go 1.26 リリースパーティ

Slide 2

Slide 2 text

mazrean ■ Goでツール等を作っている ● DIツールKessoku ● SQL Builder GenORM ■ SRE @DeNA @mazrean22 マズリーン 2

Slide 3

Slide 3 text

今日の話題 Sliceアロケーション改善

Slide 4

Slide 4 text

Release Noteになくない? と思った人もいるかも

Slide 5

Slide 5 text

状況に応じ、スタックにSliceを確保する高速化を追加

Slide 6

Slide 6 text

目次 6 1 スタック・ヒープとSlice 2 1.26でのSliceアロケーションの変化 3 変化のトレードオフ 4 まとめ

Slide 7

Slide 7 text

目次 7 1 スタック・ヒープと Slice 2 1.26でのSliceアロケーションの変化 3 変化のトレードオフ 4 まとめ

Slide 8

Slide 8 text

1.26のSliceアロケーション改善 スタック・ヒープ の使い方が改善 ■ ヒープ確保 が多発する場合があった ● 十分ありうるケース ■ 1.26で確保回数が大幅に減少 ● 最大3割減 8

Slide 9

Slide 9 text

スタック・ヒープ 多くの言語ではメモリを2領域に分けて管理 ■ スタック: 一時的なデータの保存先 ■ ヒープ: 長期的なデータの保存先 9

Slide 10

Slide 10 text

スタック 関数内でのみ使用される変数の保存先 ■ 関数 return 時に解放 ■ 確保・解放が高速 10 スタック func main() x

Slide 11

Slide 11 text

スタック 関数内でのみ使用される変数の保存先 ■ 関数 return 時に解放 ■ 確保・解放が高速 11 スタック func main() x func a() y 確保

Slide 12

Slide 12 text

スタック 関数内でのみ使用される変数の保存先 ■ 関数 return 時に解放 ■ 確保・解放が高速 12 スタック func main() x func a() y func b() z 確保

Slide 13

Slide 13 text

スタック 関数内でのみ使用される変数の保存先 ■ 関数 return 時に解放 ■ 確保・解放が高速 13 スタック func main() x func a() y func b() z 解放

Slide 14

Slide 14 text

スタック 関数内でのみ使用される変数の保存先 ■ 関数 return 時に解放 ■ 確保・解放が高速 14 スタック func main() x func a() y 解放

Slide 15

Slide 15 text

ヒープ 関数外でも使用される変数の保存先 ■ ポインタがなくなった後に解放 ● Garbage Collection ■ 確保・解放が遅い 15 ヒープ スタック func main() func a() y func b() z 関数外参照

Slide 16

Slide 16 text

エスケープ解析 基本的にコンパイラ がヒープに置く変数決定 ■ 関数外参照 の可能性がある場合、ヒープ ■ -gcflags=”-m”で確認可能 16

Slide 17

Slide 17 text

Sliceのメモリ配置 ヘッダーが配列のポインタを保持 ■ ヘッダーは他にlen・capを持つ 17 配列 ヘッダー len cap

Slide 18

Slide 18 text

Sliceのappend capを超えたら新しい配列確保(growslice) ■ データがコピーされる ■ 大量のメモリ確保 が発生 18 growslice

Slide 19

Slide 19 text

目次 19 1 スタック・ヒープとSlice 2 1.26でのSliceアロケーションの変化 3 変化のトレードオフ 4 まとめ

Slide 20

Slide 20 text

1.26 以前 関数外参照ありSliceは最初からヒープに確保 20 ヒープ スタック func main() func a()

Slide 21

Slide 21 text

問題点 append連続時にヒープ確保 が繰り返される ■ growsliceが連続 ■ 速度低下につながる 21 ヒープ スタック func main() func a() growslice growslice

Slide 22

Slide 22 text

1.26以降 Sliceの配列を最初はスタックに確保 ■ 必要ならreturn時にヒープへコピー ■ ヒープ確保 が大幅減 22 ヒープ スタック func main() func a() growslice コピー

Slide 23

Slide 23 text

ベンチマーク ■ []intに1,000,000回append ■ 1.25.6と1.26.0で比較 ■ 環境 ● CPU: AMD Ryzen 9 7950X ● メモリ: DDR5 24GB x 2 23 コード:https://gist.github.com/mazrean/bd336266c955ee52ed62e3cbec48f251

Slide 24

Slide 24 text

ベンチマーク(capなし) 速度向上 ■ アロケーション回数減少 ■ 約20%実行時間削減 24 実行時間 アロケーション回数 1.25.6 10,655,058 ns 40 1.26.0 8,219,547 ns 38

Slide 25

Slide 25 text

目次 25 1 スタック・ヒープとSlice 2 1.26でのSliceアロケーションの変化 3 変化のトレードオフ 4 まとめ

Slide 26

Slide 26 text

常に速くなるのか? cap付き確保の場合、オーバーヘッド が入る ■ growsliceが発生しない ■ ヒープコピー のみ追加 26 ヒープ スタック func main() func a() コピー

Slide 27

Slide 27 text

ベンチマーク(capあり) ほんの少し遅くなる ■ 改善と比べたら極めて小さい ● 2,000,000ns改善⇔100,000ns悪化 ■ cap付きの方が速いのは変わらず 27 実行時間 アロケーション回数 1.25.6 534,803 ns 1 1.26.0 646,919 ns 1

Slide 28

Slide 28 text

目次 28 1 スタック・ヒープとSlice 2 1.26でのSliceアロケーションの変化 3 変化のトレードオフ 4 まとめ

Slide 29

Slide 29 text

まとめ ■ スライスのメモリ確保最適化 が入った ● ヒープ確保の削減 ■ 多くの場合で速度向上 ■ トレードオフ を考慮して導入されている ● 僅かに速度低下する場合もある ● cap付きの方が速いのは変わらず 29

Slide 30

Slide 30 text

余談: コード中ドキュメント コードコメントで丁寧な説明がされている ■ Release Noteに書いてほしかった… 30 https://cs.opensource.google/go/go/+/master:src/cmd/compile/internal/slice/slice.go;l=7-111?q= slice.go&ss=go%2Fgo

Slide 31

Slide 31 text

宣伝1(個人): Go Proposal Weekly Digest Proposalを追うためのサイト作っています ■ RSSもあります ■ ステータス変動 の経緯が追える 31 https://go-proposal-weekly-digest.mazrean.com/

Slide 32

Slide 32 text

宣伝2(会社): DeNA.go ~ 1.26 Deep Dive ~ Go 1.26をワイワイ話して 深掘る(Not LT) ■ Release Note見て議論・深掘り をする ● 初心者歓迎 ■ 明日開催 32 https://dena.connpass.com/event/381352/

Slide 33

Slide 33 text

宣伝3(会社): Go Junction DeNA・Cyber Agent・DMMのGo同LT会 ■ DeNAからは以下2つの発表 ● IRIAMのギフト機能の開発・運用の裏側 ● ミューテーションテストツール ■ 残り枠僅か! 33 https://cyberagent.connpass.com/event/381653/