katsuの駄日記

Go言語のsliceのappendについて

Go言語のチュートリアルの A Tour of GoAppending to a slice について、 なんで、

len=5 cap=8 [0 1 2 3 4]

と、capacityが8になるんだー、と悩んでしまったのでちょっと調べてみた。

実験としては、以下のコードを書いて実行したみた感じです。

package main

import "fmt"

func main() {

    s := make([]int, 0)
    printSlice(s)

    for i := 0; i < 100; i++ {
        s = append(s, i)
        printSlice(s)
    }

    s = make([]int, 0)
    printSlice(s)

    for i := 0; i < 100; i++ {
        new_s := make([]int, len(s)+1)
        copy(new_s, s)
        new_s[i] = i + 1
        s = new_s
        printSlice(s)
    }
}

func printSlice(s []int) {
    fmt.Printf("len:%d cap:%d\n", len(s), cap(s))
}

で、結果は、

len:0 cap:0
len:1 cap:1
len:2 cap:2
len:3 cap:4
len:4 cap:4
len:5 cap:8
len:6 cap:8
len:7 cap:8
len:8 cap:8
len:9 cap:16

... snip ...

len:16 cap:16
len:17 cap:32

... snip ...

len:32 cap:32
len:33 cap:64

... snip ...

len:64 cap:64
len:65 cap:128

... snip ...

len:100 cap:128
len:0 cap:0
len:1 cap:1
len:2 cap:2
len:3 cap:3
len:4 cap:4
len:5 cap:5

... snip...

len:31 cap:31
len:32 cap:32
len:33 cap:33
len:34 cap:34
len:35 cap:35
len:36 cap:36

... snip...

len:98 cap:98
len:99 cap:99
len:100 cap:100

となりました。 どうも、Cで malloc() とか使うときよろしく、Goでも二倍ずつ(?)余裕をもたせてメモリを確保していく感じみたい。

と、上の内容をQiitaででも書いてやろうかと思ったのですが、 こちらのページ にて以下の記述が!!

まずはnewcapの計算から見てみよう。newcapは拡張後の新しいcapの値になる。newcapの計算処理は次の様なロジックだ。

  1. 現在のcapの2倍の値が求められているcapよりも小さければ、求められているcapをnewcapにする
  2. lenが1024より短い場合は現在のcapを2倍にしていき、求められているcapよりも大きくなったらその値をnewcapにする
  3. lenが1024よりも長ければ1/4ずつ増やしていく

つまり、1024より短い時は今のcapの2倍以上、1024よりも長ければ1.25倍以上にするようになっている。これが良く聞くGoのsliceは2倍ずつ増えていく、の実装である。

ということで、Goのsliceが2倍ずつ増えていくというのは、とてもメジャーなお話らしい。 そんなわけで、Qiitaに書かずにこちらに記載しておきました……。

misc Golang