読者です 読者をやめる 読者になる 読者になる

今川館

都内勤務の地味OLです

runtime.MemStatsでメモリ使用量を調べる&pprofモジュール

ポインタのことを理解するためにメモリの使用量を調べながら色々試そうと思ったが、どうやってやるのか分からないので調べた。

Understanding Go Lang Memory Usage · deferpanic eng blog

ここに詳しく書いてある。

  • 単にメモリの使用量を調べるだけならruntime.MemStatsで判る。
  • pprofモジュールを使うとtopコマンドみたいな詳細な出力を簡単に得られる。

runtime.MemStatsでメモリ使用量を調べる

サンプルコードとして「StringBuffer」というクラスを作って、Appendのメソッドチェーンでどんどん文字列を書き足していくプログラムを書いた。
そして処理前後でMemStatsから得られる情報を見比べて、使用量メモリが増えていることを確かめる。

package main

import (
	"bytes"
	"fmt"
	"runtime"
)

type StringBuffer struct {
	buf  bytes.Buffer
	size int
	err  error
}

func (s *StringBuffer) Append(text string) *StringBuffer {
	if s.err != nil {
		return s // Nothing to do.
	}
	size, err := s.buf.WriteString(text)
	s.size += size
	s.err = err
	return s
}

func (s *StringBuffer) String() string {
	return s.buf.String()
}

func NewBuffer() StringBuffer {
	return StringBuffer{}
}

const LF = "\n"

var mem runtime.MemStats

func PrintMemory() {
	runtime.ReadMemStats(&mem)
	fmt.Println(mem.Alloc, mem.TotalAlloc, mem.HeapAlloc, mem.HeapSys)
}

func main() {
	PrintMemory() // 47504 47504 47504 884736

	buf := NewBuffer()

	for i := 0; i < 25000; i++ {
		buf.Append("Hello").Append(", ").Append("world.")
		buf.Append(LF).Append("I have a ").Append("pen.")
	}
	fmt.Println(buf.size, buf.err) // 675000 <nil>

	PrintMemory() // 2348200 2348200 2348200 3112960
}

出力内容

48336 48336 48336 884736
675000 <nil>
2349032 2349032 2349032 3112960

確かに増えている。

MemStatsで得られる情報は、ドキュメントのMemStatsTypeの項に列挙されているが数が多いので今回使ったものだけ引用。

runtime パッケージ - golang.jp

// 一般統計
// 更新時にロックはされないため、近似値です。
Alloc uint64 // アロケートされ、使用中のバイト数
TotalAlloc uint64 // アロケート済みのバイト数(解放された数も含む)

// 主要なヒープ割り当て統計
HeapAlloc uint64 // ロケートされ、使用中のバイト数
HeapSys uint64 // システムから取得したバイト数

だそうです。

書き込み用のファイルライクオブジェクトはbytes.Buffer

読み取り専用ファイルライクオブジェクトはstrings.NewReaderとbytes.NewReaderの両方が用意されているが、書き込み用はなぜかbytes.Bufferしか無かったのでこれを使った。

文字列を書き込む「WriteString」メソッド、byteを書き込む「WriteByte」、byteのスライスを書き込む「Write」などのメソッドがあるので、今回はWriteStringを使った。

pprofモジュールを使うとより視覚的なビューを利用できる

更に、pprofモジュールを使うとLinuxのtopコマンドみたいな詳細な出力を得られるらしい。

golangで書かれたプログラムのメモリ使用状況を見る - hakobe-blog ♨
Understanding Go Lang Memory Usage · deferpanic eng blog

ふたつの記事を参考に試してみた。

[foo/main.go]

package main

import (
	"log"
	"net/http"
	_ "net/http/pprof"
	"sync"
)

func main() {
	var wg sync.WaitGroup

	go func() {
		log.Println(http.ListenAndServe("localhost:6060", nil))
	}()

	wg.Add(1)
	wg.Wait()
}
$ cd foo
$ go build -o cmd
$ cmd  # 待機

ブラウザで所定のURLを問い合わせると詳細な情報が出力される。

f:id:imagawa_yakata:20161213140635p:plain

sync.WaitGroupとは?

これは、ひとつ以上のgoroutineを予約語goを使って動かして、すべてが終了するまで待ちたいときに使うものらしい。

sync - The Go Programming Language

type WaitGroup

A WaitGroup waits for a collection of goroutines to finish. The main goroutine calls Add to set the number of goroutines to wait for. Then each of the goroutines runs and calls Done when finished.

Big Sky :: golang の sync パッケージの使い方

sync.WaitGroup は Wait() を呼び出すと Add() を呼び出した回数から Done() を呼び出した回数を引いて 0 になるまで待機する機能が簡単に実装出来ます。全ての goroutine の終了を待つ場合に使用します。

サンプルコードでは、Add(1)してDoneを一切呼ばないので、ずっと待つことになる。