Go: 大きなスライスの一部を保持すると元の配列ごと残る(slices.Clone で切り離す)
hdr := buf[:64] は 64 バイトのコピーではなく、10MB の buf と同じ下の配列を指す。hdr をキャッシュなどに長く持つと、GC は 10MB 全体を解放できない。長く使う部分はコピーしてから保持する。
どんな時に起きる?
- 大きなファイルやレスポンスを
[]byteに読み、ヘッダーや ID だけ切り出して map やキャッシュに入れる - 巨大な文字列から
s[i:j]で部分文字列を取り出して保持する(string も同じ仕組み) - ヒーププロファイルで、読み込み関数の割り当てが解放されずに残り続ける
書き方
go
data, _ := os.ReadFile(path) // 10MB
// NG: data 全体が cache から参照され続ける
cache[key] = data[:64]
// OK: 必要な分だけ新しい配列にコピーする
cache[key] = bytes.Clone(data[:64]) // Go 1.20+
// または slices.Clone(data[:64]) // Go 1.21+、任意の要素型
// 文字列の場合
name := strings.Clone(line[10:20]) // Go 1.20+1.20 より古い Go なら append([]byte(nil), data[:64]...) で同じことができる。
注意点
- 3 要素スライス式
data[:64:64]は容量を制限するだけで、下の配列は同じ。メモリは解放されない。 - コピーは長く保持する部分だけでよい。関数内ですぐ使い終わるなら切り出しのままで問題ない(コピーのほうが無駄)。
- ポインタを含む要素型(
[]*Tなど)では、切り出した範囲の外の要素が指すオブジェクトも残る。不要な要素は nil にしておく。