今回のゴール
ヒープやメモリや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 | サイボウズエンジニアのブログ