Память и производительность срезов
Внутренняя структура среза
┌─────────┬─────┬─────┐
│ *array │ len │ cap │
└─────────┴─────┴─────┘
│
▼
┌────┬────┬────┬────┬────┐
│ 10 │ 20 │ 30 │ 40 │ 50 │
└────┴────┴────┴────┴────┘
len— количество доступных элементов.cap— размер выделенного массива от начала среза до конца.
Рост при append
Когда len == cap, append выделяет новый массив и копирует данные:
s := make([]int, 0)
for i := 0; i < 8; i++ {
prev := cap(s)
s = append(s, i)
if cap(s) != prev {
fmt.Printf("len=%d cap: %d → %d\n", len(s), prev, cap(s))
}
}
// cap: 0→1, 1→2, 2→4, 4→8
Стратегия удвоения даёт амортизированную стоимость O(1) на один append.
Предвыделение
Если конечный размер известен — выделяй сразу:
// Плохо: многократное копирование
var result []int
for _, v := range source {
result = append(result, transform(v))
}
// Хорошо: одна аллокация
result := make([]int, 0, len(source))
for _, v := range source {
result = append(result, transform(v))
}
copy
src := []int{1, 2, 3, 4, 5}
dst := make([]int, 3)
n := copy(dst, src) // копирует min(len(dst), len(src)) элементов
fmt.Println(dst, n) // [1 2 3] 3
Утечки памяти через срезы
Подсрез держит ссылку на весь исходный массив:
// Утечка: большой массив не будет собран GC
data := readBigFile()
header := data[:10] // data остаётся в памяти
// Исправление: явная копия
header := make([]byte, 10)
copy(header, data[:10])
Упражнения
| № | Задание | Сложность |
|---|---|---|
| 1 | Упражнение 1 | easy |
| 2 | Упражнение 2 | easy |
| 3 | Упражнение 3 | medium |