append и рост
Сергей поставил импорт на поток: сто тысяч файлов за один заход. Код на этом пакете то и дело тормозил, и Миша долго искал причину. Виновником оказался сам список: он рос по одному элементу и на каждом шаге перестраивался.
append дописывает в конец слайса и при нехватке ёмкости выделяет новый массив — поэтому результат обязательно присваивать обратно, а известный заранее размер лучше закладывать в make.
Рост и переаллокация
Что здесь показано
- Стек — локальные переменные функции.
i = 3— значение;p = ptr → 10— указатель на значение;b = "hello"— срез (заголовок: указатель + длина + ёмкость). - Куча — данные, на которые ссылаются переменные:
rodata— байты строки-литерала (в бинарнике),данные N Б— бэкинг-массив слайса,строка— динамическая строка. - Горутины — лёгкие потоки Go: статус (running/runnable/waiting…), функция, в которой каждая сейчас находится, размер стека (растёт по мере работы) и пометка «из main» — горутина запущена из твоего кода.
- GC — сборщик мусора: фаза (off/mark) и число завершённых циклов.
Если в подлежащем массиве хватает места, append пишет в него. Если нет — выделяет новый, большего размера, копирует старые элементы и дописывает новые.
Результат присваивают обратно
Именно поэтому идиома — s = append(s, x). append может вернуть слайс, указывающий на другой массив, а старый заголовок останется с прежними len и cap. Результат нельзя выбрасывать: присваивай его обратно, возвращай из функции или сохраняй в другую переменную. Потеряешь возврат — потеряешь новые элементы.
Коэффициент роста — деталь реализации: маленькие слайсы растут примерно вдвое, с ростом размера коэффициент плавно снижается. Полагаться на точные цифры не стоит — они менялись между версиями. Важно поведение: рост амортизированный, append в цикле — O(1) в среднем, но с редкими скачками на переаллокацию и копирование.
Prealloc
Когда размер известен заранее, выделяй с нужной ёмкостью, чтобы не переаллоцировать по многу раз:
func fill(n int) []int {
out := make([]int, 0, n) // ёмкость задана, переаллокаций не будет
for i := 1; i <= n; i++ {
out = append(out, i)
}
return out
}
Вы узнаете:
- как дописать весь слайс разом через
append(s, slice...) - почему старый массив после переаллокации становится мусором
Проверьте себя
Пакетный импорт знает число файлов заранее. Что происходит с числом выделений массивов при n = 1000: fill с запасом make([]int, 0, 1000) против «ленивого» fillGrow без запаса?
Что выведет a := make([]int, 1, 1); a[0] = 1; a = append(a, a...); fmt.Println(a)?
правильных ответов
append растёт за счёт переаллокаций; результат нельзя выбрасывать — присваивай, возвращай или сохраняй; заранее известный размер закладывай в make.
10 000 ₽ · 60 минут · онлайн