golangのappendの速度比較

appendって毎回新しいスライスが作られているような感じに見えるので、なんか遅そうなイメージがあるんすよね。まぁ、実際には内部で参照している配列のキャパシティに余裕があれば空きに追加されるだけなのでキャパシティさえあれば高速なのだが。

では、実際にキャパシティの有無でどのくらい差が出るのか?

キャパシティ未指定の場合

const x = 200000000

func main() {
    hoge := []int{}

    for i := 0; i < x; i++ {
        hoge = append(hoge, i)
    }

    fmt.Println(hoge[0] + hoge[x-1])
}
$ time go run 1.go
199999999
go run 1.go  5.87s user 5.83s system 139% cpu 8.393 total

キャパシティを指定した場合

const x = 200000000

func main() {
    hoge := make([]int, 0, x)

    for i := 0; i < x; i++ {
        hoge = append(hoge, i)
    }

    fmt.Println(hoge[0] + hoge[x-1])
}
$ time go run 2.go
199999999
go run 2.go  0.78s user 0.71s system 135% cpu 1.104 total

7〜8倍程度の高速化。

動的言語ではキャパシティが不足したら現在の倍のキャパシティを確保という感じのアルゴリズムになっているため、こんなに差が付かないのだが、Goはそういう余計なことはしないらしい。GCありの言語のくせにこういう所では低水準言語感を示してくるなこやつw

インデックスアクセスで値をセット

const x = 200000000

func main() {
    hoge := make([]int, x)

    for i := 0; i < x; i++ {
        hoge[i] = i
    }

    fmt.Println(hoge[0] + hoge[x-1])
}
$ time go run 3.go
199999999
go run 3.go  0.81s user 0.80s system 137% cpu 1.170 total

容量確保してappendとほぼ同じ速度。

まぁ、あえてインデックスアクセスで書く理由もないのでキャパシティ確保した上でappendする方がいいと思います。

Golangの構造体、値のスライスとポインタのスライス

構造体の値のスライスとポインタのスライス、どちらがいいのか?

こんな構造体とJSON文字列を定義して…

type Hoge struct {
    Nyan int64
    Wang string
}

const jsonStr = `[{"nyan":1, "wang":"aaaa"}, {"nyan":1, "wang":"bbbb"}]`

ポインタのスライスの場合

var a []*Hoge

json.Unmarshal([]byte(jsonStr), &a)

for i, h := range a {
    h.Nyan *= 2
    h.Wang += h.Wang
        fmt.Println(h == a[i])
        fmt.Println(*h == *a[i])
}

fmt.Println(a[0].Nyan, a[0].Wang)
fmt.Println(a[1].Nyan, a[1].Wang)

結果

2 aaaaaaaa
4 bbbbbbbb

ループの中での変更は反映される。まあポインタだから当然。

値の場合

var b []Hoge

json.Unmarshal([]byte(jsonStr), &b)

for _, h := range b {
    h.Nyan *= 2
    h.Wang += h.Wang
}

fmt.Println(b[0].Nyan, b[0].Wang)
fmt.Println(b[1].Nyan, b[1].Wang)

結果

1 aaaa
2 bbbb

ループの中での変更は反映されていない。つまりループ変数の h には構造体がコピーされて渡されているということ。巨大な構造体のスライスをループさせるとコピーの負荷がそれなりに発生しそう。特に理由がなければポインタのスライスにしたほうが良さそう。