prealloc

Table of Contents

  1. 解決する問題
  2. 設定
  3. オプション
  4. サンプル
    1. 検出例
    2. 修正例
  5. 注意点
  6. 参考リンク

prealloc はスライスの事前割り当て(pre-allocation)が可能な箇所を検出するツールです。

解決する問題

append を繰り返すと、スライスの容量が不足するたびに新しい配列が確保されコピーが発生します。ループ回数が事前に分かる場合は make([]T, 0, n) で容量を確保しておくことで、不要なメモリ再割り当てを防げます。

1
2
3
4
5
6
7
8
// Before: append のたびに再割り当てが発生する可能性
func CollectNames(users []User) []string {
var names []string
for _, u := range users {
names = append(names, u.Name)
}
return names
}
1
2
3
4
5
6
7
8
// After: 事前に容量を確保
func CollectNames(users []User) []string {
names := make([]string, 0, len(users))
for _, u := range users {
names = append(names, u.Name)
}
return names
}

設定

公式ドキュメント

1
2
3
4
5
6
7
8
9
10
11
# .golangci.yml 設定例
version: "2"

linters:
enable:
- prealloc
settings:
prealloc:
simple: true
range-loops: true
for-loops: false

オプション

オプション デフォルト 説明
simple bool true 単純なループ(条件分岐なし)のみをチェックする
range-loops bool true range ループをチェック対象にする
for-loops bool false 通常の for ループもチェック対象にする

サンプル

検出例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// prealloc が警告するパターン

// 1. range ループでの append(simple: true)
func GetIDs(items []Item) []int {
var ids []int // Consider pre-allocating `ids`
for _, item := range items {
ids = append(ids, item.ID)
}
return ids
}

// 2. フィルタリングを含むループ(simple: false の場合のみ検出)
func GetActiveIDs(items []Item) []int {
var ids []int
for _, item := range items {
if item.Active {
ids = append(ids, item.ID)
}
}
return ids
}

// 3. for ループ(for-loops: true の場合のみ検出)
func GenerateSequence(n int) []int {
var seq []int
for i := 0; i < n; i++ {
seq = append(seq, i)
}
return seq
}

修正例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
func GetIDs(items []Item) []int {
ids := make([]int, 0, len(items))
for _, item := range items {
ids = append(ids, item.ID)
}
return ids
}

// フィルタリングがある場合でも最大容量で確保
func GetActiveIDs(items []Item) []int {
ids := make([]int, 0, len(items))
for _, item := range items {
if item.Active {
ids = append(ids, item.ID)
}
}
return ids
}

func GenerateSequence(n int) []int {
seq := make([]int, 0, n)
for i := 0; i < n; i++ {
seq = append(seq, i)
}
return seq
}

注意点

  • simple: true(デフォルト)では条件分岐のないループのみ検出する。条件付き append も検出したい場合は simple: false にする
  • フィルタリングがある場合、事前割り当てした容量は実際に使う量より多くなるが、再割り当てのコストを避けるトレードオフとして許容される
  • 小さなスライスやループ回数が少ない場合は事前割り当ての効果が小さいため、パフォーマンスが重要な箇所に集中して適用するのが効果的
  • テストファイルでは無効化するのが一般的(テストの可読性を優先)

参考リンク