伸長するsliceの取扱い

Goで何かしら値を計算した結果をsliceに格納するときにcapを事前に設定出来ない場合もある。

// capがわかってない場合
var slice []T
// cap(N)がわかっている場合
slice := make([]T, 0, n)

append時にcapが足りない場合、slice内部の値をコピーして伸長する。 そのため、capを事前に設定できない場合、sliceに格納するstructは実態ではなく参照にしておくことが望ましい。

package slice

import (
    "testing"
)

type T struct {
    A int
}

type T2 struct {
    A int
    B string
}

type T3 struct {
    A int
    B string
    C []string
}

func BenchmarkReal(b *testing.B) {
    var slices []T
    for i := 0; i < b.N; i++ {
        t := T{A: 1}
        slices = append(slices, t)
    }

}

func BenchmarkReal2(b *testing.B) {
    var slices []T2
    for i := 0; i < b.N; i++ {
        t := T2{A: 1, B: "B"}
        slices = append(slices, t)
    }
}

func BenchmarkReal3(b *testing.B) {
    var slices []T3
    for i := 0; i < b.N; i++ {
        t := T3{A: 1, B: "B", C: []string{"C"}}
        slices = append(slices, t)
    }

}

func BenchmarkPointer(b *testing.B) {
    var slices []*T
    for i := 0; i < b.N; i++ {
        t := &T{A: 1}
        slices = append(slices, t)
    }
}

func BenchmarkPointer2(b *testing.B) {
    var slices []*T2
    for i := 0; i < b.N; i++ {
        t := &T2{A: 1, B: "B"}
        slices = append(slices, t)
    }
}

func BenchmarkPointer3(b *testing.B) {
    var slices []*T3
    for i := 0; i < b.N; i++ {
        t := &T3{A: 1, B: "B", C: []string{"C"}}
        slices = append(slices, t)
    }
}
$ go test -bench=. -benchmem                                                                                    [~/src/github.com/yoppi/go]
goos: darwin
goarch: amd64
pkg: github.com/yoppi/go
BenchmarkReal-4         100000000               14.2 ns/op            49 B/op          0 allocs/op
BenchmarkReal2-4         5000000               220 ns/op             132 B/op          0 allocs/op
BenchmarkReal3-4         3000000               551 ns/op             272 B/op          1 allocs/op
BenchmarkPointer-4       5000000               235 ns/op              51 B/op          1 allocs/op
BenchmarkPointer2-4     10000000               220 ns/op              74 B/op          1 allocs/op
BenchmarkPointer3-4      5000000               240 ns/op             107 B/op          2 allocs/op
PASS
ok      github.com/yoppi/go     10.506s

興味深いのは、フィールドがint一つだけの場合参照ではなく実態の方が高速に扱えていること。このあたりもうちょっと調べる。