今回のゴール
ヒープやメモリやGCの状態を関数内に配置して、数値で観測してみた。
実装
最終的なコードは以下です。
package main
import (
"fmt"
"runtime"
)
// 状態を出力する関数
func printMemStats() {
var m runtime.MemStats
runtime.ReadMemStats(&m)
// 現在のヒープの状態を出力
fmt.Printf("Alloc = %v MiB", bToMb(m.Alloc))
// ヒープに割り当てられた累計量
fmt.Printf("\tTotalAlloc = %v MiB", bToMb(m.TotalAlloc))
// システムがGoランタイムに割り当てたメモリ量
fmt.Printf("\tSys = %v MiB", bToMb(m.Sys))
// そしてガベージコレクションの回数
fmt.Printf("\tNumGC = %v\n", m.NumGC)
}
// バイトをメガバイトに変換するヘルパー関数
func bToMb(b uint64) uint64 {
return b / 1024 / 1024
}
// メイン関数
func main() {
printMemStats()
// 思い処理なので注意
data := make([]int, 1<<25) // 2^25要素のスライスを作成
for i := range data {
data[i] = i
}
printMemStats()
}
// Alloc = 0 MiB TotalAlloc = 0 MiB Sys = 6 MiB NumGC = 0
// Alloc = 256 MiB TotalAlloc = 256 MiB Sys = 267 MiB NumGC = 1
-
func printMemStats()
:printMemStats
という名前の関数を定義しています。この関数は引数を取らず、何も返しません。 -
var m runtime.MemStats
:runtime.MemStats
型の新しい変数m
を宣言しています。この型はGoランタイムが使用しているメモリに関する統計情報を保持します。 -
runtime.ReadMemStats(&m)
:ReadMemStats
関数を呼び出して、現在のメモリ統計情報を取得し、それを先ほど宣言した変数m
に格納しています。 -
fmt.Printf("Alloc = %v MiB", bToMb(m.Alloc))
:Alloc
フィールド(現在ヒープに割り当てられているバイト数)の値をメガバイト単位で表示しています。これはbToMb
関数を使用してバイトからメガバイトに変換されます。 -
fmt.Printf("\tTotalAlloc = %v MiB", bToMb(m.TotalAlloc))
: 同様に、TotalAlloc
フィールド(ヒープに割り当てられた累計バイト数)の値も表示しています。 -
fmt.Printf("\tSys = %v MiB", bToMb(m.Sys))
:Sys
フィールド(Goランタイムがシステムから取得した合計メモリ)の値も表示しています。 -
fmt.Printf("\tNumGC = %v\n", m.NumGC)
: 最後に、NumGC
フィールド(これまでに実行されたガベージコレクションの回数)の値を表示しています。 -
func bToMb(b uint64) uint64 { return b / 1024 / 1024 }
: バイトをメガバイトに変換するための補助関数bToMb
を定義しています。この関数は64ビット符号なし整数を引数に取り、同じく64ビット符号なし整数を返します。 -
func main() { printMemStats()
: メイン関数で、最初にprintMemStats()
関数を呼び出して現在のメモリ統計情報を表示します。 -
data := make([]int, 1<<25)
: 2^25要素(約3300万要素)のスライスを作成し、それを変数data
に格納します。これは大量のメモリを消費する処理です。 -
for i := range data { data[i] = i }
: 作成したスライスの各要素にそのインデックスと同じ値を設定します。 -
printMemStats() }
: 最後に再度printMemStats()
関数を呼び出して、メモリ消費が多い処理後のメモリ統計情報を表示します。
ヒープとは
- Goコンパイラでメモリを確保できない値がヒープにエスケープされる。
- GCされる。
- Goコンパイラとランタイムによって、いつ使用され、いつクリーンアップされるか仮定できない。
- サイズが動的に決定される。
スタックとは
- ローカル変数で、ポインタではないGoの値が割り当てられる。
- GCされない。
- GCを使わないので効率的。
- レキシカルスコープに結びついたメモリが割り当てられる。
- Goコンパイラが、メモリを確保する。
- goroutineスタックに格納される。
所感
GC観測できて、すごってなりました。発言できたのは、プログラムが思ったより重い処理だったのでメモリ解放を起こしたのかなと思っています。
スタックも出力できる
Go 製ソフトウェアでメモリ使用量の多い関数を特定する - Cybozu Inside Out | サイボウズエンジニアのブログ