← Назад к вопросам

Как выделяется память под slice?

3.0 Senior🔥 61 комментариев
#Операционные системы и Linux#Производительность и оптимизация

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Механизм выделения памяти под slice в Go

Slice (срез) в Go — это **абстракция над массивом**, предоставляющая более гибкий интерфейс для работы с последовательностями данных. Выделение памяти под slice — многоэтапный процесс, зависящий от способа создания.

Базовая структура slice

Под капотом slice представлен структурой из трёх полей:

type sliceHeader struct {
    ptr   *byte    // Указатель на первый элемент базового массива
    len   int      // Текущая длина (количество элементов)
    cap   int      // Ёмкость (максимальное количество элементов без реаллокации)
}

Способы создания и выделения памяти

1. Объявление без инициализации

var s []int

В этом случае создаётся nil-slice: ptr = nil, len = 0, cap = 0. Память под базовый массив не выделяется.

2. Создание через литерал

s := []int{1, 2, 3, 4, 5}

Компилятор:

  • Выделяет массив из 5 элементов в куче (heap)
  • Инициализирует его значениями {1,2,3,4,5}
  • Создает slice, где ptr указывает на массив, len = 5, cap = 5

3. Использование make()

s := make([]int, 5, 10)

Это наиболее контролируемый способ:

  • Выделяется базовый массив ёмкостью 10 элементов
  • Первые 5 элементов инициализируются нулевыми значениями (0 для int)
  • len = 5, cap = 10

Если опустить третий аргумент:

s := make([]int, 5) // len = 5, cap = 5

Динамическое расширение (reallocation)

При добавлении элементов сверх ёмкости происходит реаллокация:

s := []int{1, 2, 3}  // len=3, cap=3
s = append(s, 4)     // Требуется увеличение ёмкости

Алгоритм реаллокации:

  1. Go создаёт новый базовый массив с увеличенной ёмкостью
  2. Старые элементы копируются в новый массив
  3. Добавляется новый элемент
  4. Старый массив остаётся в памяти до сборки мусора

Правила увеличения ёмкости (до Go 1.18 и после):

  • До Go 1.18: при cap < 1024 — удвоение, при cap >= 1024 — увеличение на 25%
  • После Go 1.18: более сложная формула с переходом от 2x к 1.25x

Критические аспекты управления памятью

Влияние на производительность

Частые реаллокации дорогостоящи. При известном размере лучше указывать ёмкость:

// Неэффективно
var data []int
for i := 0; i < 1000; i++ {
    data = append(data, i) // Многократные реаллокации
}

// Эффективно
data := make([]int, 0, 1000)
for i := 0; i < 1000; i++ {
    data = append(data, i) // Реаллокаций не будет
}

Ссылки на базовый массив

Несколько срезов могут разделять один базовый массив:

arr := []int{1, 2, 3, 4, 5}
s1 := arr[1:3] // [2, 3], cap=4
s2 := arr[2:4] // [3, 4], cap=3

s1[1] = 99 // Изменяется и arr[2], и s2[0]

Особенности под-срезов (sub-slicing)

original := make([]int, 5, 10) // Базовый массив на 10 элементов
sub := original[2:5]           // len=3, cap=8 (10-2)

Ёмкость под-среза рассчитывается как cap(original) - offset.

Сборка мусора и утечки памяти

Базовый массив не освобождается, пока на него есть ссылки. Это может вызывать утечки памяти:

func getLastNBytes(data []byte, n int) []byte {
    return data[len(data)-n:] // Удерживает весь исходный массив!
}

// Правильная реализация с копированием:
func getLastNBytesSafe(data []byte, n int) []byte {
    result := make([]byte, n)
    copy(result, data[len(data)-n:])
    return result
}

Оптимизации компилятора

Go компилятор выполняет несколько оптимизаций:

  • Escape analysis: определяет, можно ли выделить массив на стеке
  • Bounds check elimination: удаляет избыточные проверки границ
  • Small slice optimization: для tiny-срезов использует специализированные аллокаторы

Практические рекомендации

  1. Используйте make() с предварительным указанием ёмкости, когда размер известен
  2. Избегайте ненужных копий больших срезов
  3. Помните о разделении базового массива при создании под-срезов
  4. Используйте copy() для явного копирования, когда нужно разорвать связь
  5. Мониторьте cap и len в профилировщике для выявления неоптимальных аллокаций

Понимание механизмов выделения памяти под срезы критически важно для написания эффективных программ на Go, особенно при работе с большими объёмами данных или в высоконагруженных системах.