Материал проходит вычитку — возможны неточности.
Урок 8 из 118

Backing storage: почему маленькая строка может держать большой массив

Ночью сервис съел двенадцать гигабайт памяти. Аня подняла тревогу, и к утру нашли виновника: крошечный ID, вырезанный из огромного лога, держал в памяти весь файл целиком.

Главная мысль

подстрока — новое окно на тот же массив байтов, поэтому маленькая строка может удерживать в памяти большой исходник; отвязывают её копированием — strings.Clone.

В прошлом уроке мы увидели, что подстрока не копирует байты, а ссылается на ту же память — backing storage исходной строки. У этой оптимизации есть обратная сторона.

Пример: 100 байт держат гигабайт

Возьмём большой текст и маленький кусок из него:

data := readHugeFile() // допустим, 1 ГБ
header := data[:100]

header содержит всего 100 байт, но ссылается на то же backing storage, что и гигабайтный data:

header
┌───────────┐
│ 100 байт  │
└─────┬─────┘
      │
      ▼
┌─────────────────────────────────────────┐
│               1 ГБ данных               │
└─────────────────────────────────────────┘

Пока жив заголовок header, сборщик мусора не может собрать backing storage: он ссылается на тот же массив, что и большой data. Программа использует 100 байт, а держит в памяти гигабайт.

Это особенно неприятно, если маленькие подстроки сохраняются надолго. Например, сервер читает большой файл, извлекает из него ID и складывает ID в кеш:

func getID(data string) string {
    return data[:100]
}

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

Как отвязать: strings.Clone

Когда нужна только маленькая часть большого текста, делай независимую копию:

header := strings.Clone(data[:100])

Теперь у header своё маленькое хранилище, и большой массив освободится, как только на него не останется других ссылок.

Когда копировать

Копирование тоже стоит денег: выделить память и скопировать байты. Обычный срез часто — именно то, что нужно: если подстрока живёт несколько микросекунд внутри функции, копия ради отвязки не нужна.

Копируй, когда одновременно верны два условия:

  1. исходная строка очень большая;
  2. маленькая подстрока живёт значительно дольше исходника (кеш, глобальная структура).
В следующем уроке

Вы узнаете:

  • как та же логика удержания работает у слайсов
  • как отвязывали строку до появления strings.Clone
Задание · вопросы

Проверьте себя

Вопрос 1 из 3

Аня просит сформулировать правило копирования одним предложением. Какой вариант верный?

Вопрос 2 из 3

Из файла на 500 МБ извлекается 20-байтный ID и сохраняется в кеш на месяцы. Нужен ли strings.Clone?

Вопрос 3 из 3

Что делает strings.Clone(data[:100])?

Итоги

Подстрока держит весь backing storage исходника живым, пока жив её заголовок. Дешёвый срез хорош для короткоживущих данных; когда маленький результат должен жить долго, а исходник огромный — отвяжи его strings.Clone.

Наш сайт собирает куки. Чтобы тебе было удобнее им пользоваться. Нажимая на кнопку, ты даёшь разрешение собирать куки