2012/09/03

go言語のsliceと make() は賢い

go言語のsliceとは

スライスはある配列内の連続した領域への参照であり、スライスの内容はその配列の要素の並びです。
スライス型は、その要素型と同じ要素型を持つ配列のすべてのスライスの集合を表します。
初期化されていないスライス型の値はnilです。
配列のように、スライスはインデックスによる指定が可能で長さを持ちます。 Goプログラミング言語仕様
と説明がある。 ざっくり言えば『配列の部分参照』と『可変長の配列』として利用できる。

今回は『可変長の配列』として利用する方法を書く。

長さ 0 の slice を作成する

以下のどちらの書き方でもOK。
values := []int{}
values := make([]int, 0)

要素を追加する

append 文で要素は追加できる。
values = append(values, 0) // 0 を追加する values の中身は [0]
values = append(values, 1) // 1 を追加する values の中身は [0, 1]

このときのメモリの再割当てはどうなってる?

cap() で slice の容量(capacity) の増え方を確認してみると、Go言語の append() 文はなかなか利口。
values := []int{}
fmt.Println(cap(values))
values = append(values, 0)
fmt.Println(cap(values))
values = append(values, 1)
fmt.Println(cap(values))
values = append(values, 2)
fmt.Println(cap(values))
values = append(values, 3)
fmt.Println(cap(values))
values = append(values, 4)
fmt.Println(cap(values))
values = append(values, 5)
fmt.Println(cap(values))
values = append(values, 6)
fmt.Println(cap(values))
の結果は、
0
1
2
4
4
8
8
8
と2のn乗で徐々にメモリが拡張されているのがわかる。

要素数の予測がつくなら、メモリの拡張回数を制御できる

あらかじめ要素数の予測がつくなら、capacity を指定して make() するとメモリ効率は良くなる。
values := make([]int, 0, 3) // len(values) = 0, cap(values) = 3 の可変長配列を作る
fmt.Println(cap(values))
values = append(values, 0)
fmt.Println(cap(values))
values = append(values, 1)
fmt.Println(cap(values))
values = append(values, 2)
fmt.Println(cap(values))
values = append(values, 3)
fmt.Println(cap(values))
values = append(values, 4)
fmt.Println(cap(values))
values = append(values, 5)
fmt.Println(cap(values))
values = append(values, 6)
fmt.Println(cap(values))
の結果は、
3
3
3
3
6
6
6
12
と指定した capacity * 2のn乗で拡張される。

雑感

これまで C++ や Delphi を使ってきてたこともあり、1要素ずつ追加されるんじゃメモリ管理も書かなきゃかな?と思ったり、container/list を使わなきゃかな?と思ってみたりしてたけど、この程度のことなら言語任せでOK。
むしろ言語任せにすべき。
おかげでコードがシンプルで助かる。
まだ Go言語に慣れたとは言いがたいけど、慣れれば慣れるほどコードが短くなってくのが面白い。

0 件のコメント:

コメントを投稿