goコンパイラの最適化によって、可変長配列であってもリテラル値でlength指定されていればコンパイルする時点で確保するデータサイズが決定し結果的にランタイム時にアロケーションを発生させなくなるのではないかという仮説。
テストコード
package app import ( "bytes" "testing" ) const ALLOC_SIZE = 64 * 1024 func BenchmarkFunc1(b *testing.B) { for i := 0; i < b.N; i++ { v := make([]byte, ALLOC_SIZE) fill(v, '1', 0, ALLOC_SIZE) } } func BenchmarkFunc2(b *testing.B) { for i := 0; i < b.N; i++ { b := new(bytes.Buffer) b.Grow(ALLOC_SIZE) fill(b.Bytes(), '2', 0, ALLOC_SIZE) } } func fill(slice []byte, val byte, start, end int) { for i := start; i < end; i++ { slice = append(slice, val) } }
これを実行した結果が以下。
at 19:05:47 ❯ go test -bench . -benchmem -gcflags=-m # app [app.test] ./main_test.go:25:6: can inline fill ./main_test.go:10:6: can inline BenchmarkFunc1 ./main_test.go:13:7: inlining call to fill ./main_test.go:20:9: inlining call to bytes.(*Buffer).Grow ./main_test.go:21:15: inlining call to bytes.(*Buffer).Bytes ./main_test.go:21:7: inlining call to fill ./main_test.go:10:21: b does not escape ./main_test.go:12:12: make([]byte, ALLOC_SIZE) escapes to heap ./main_test.go:20:9: BenchmarkFunc2 ignoring self-assignment in bytes.b.buf = bytes.b.buf[:bytes.m·3] ./main_test.go:17:21: b does not escape ./main_test.go:19:11: new(bytes.Buffer) does not escape ./main_test.go:25:11: slice does not escape # app.test /var/folders/45/vh6dxx396d590hxtz7_9_smmhqf0sq/T/go-build1328509211/b001/_testmain.go:35:6: can inline init.0 /var/folders/45/vh6dxx396d590hxtz7_9_smmhqf0sq/T/go-build1328509211/b001/_testmain.go:43:24: inlining call to testing.MainStart /var/folders/45/vh6dxx396d590hxtz7_9_smmhqf0sq/T/go-build1328509211/b001/_testmain.go:43:42: testdeps.TestDeps{} escapes to heap /var/folders/45/vh6dxx396d590hxtz7_9_smmhqf0sq/T/go-build1328509211/b001/_testmain.go:43:24: &testing.M{...} escapes to heap goos: darwin goarch: amd64 pkg: app cpu: Intel(R) Core(TM) i7-7700HQ CPU @ 2.80GHz BenchmarkFunc1-8 8565 118348 ns/op 393217 B/op 4 allocs/op BenchmarkFunc2-8 23332 53043 ns/op 65536 B/op 1 allocs/op PASS ok app 2.902s
うーむ make([]byte, ALLOC_SIZE) escapes to heap
と言われているのでヒープに乗ってしまった。仮説は間違ってたっぽい。
結局比較に使っている new(bytes.Buffer)
のほうがアロケーションの回数も使うメモリの量も小さいと出た。go力が低すぎて理由が分からない。
(答え合わせの続編↓) izumisy.work