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 своё маленькое хранилище, и большой массив освободится, как только на него не останется других ссылок.
Когда копировать
Копирование тоже стоит денег: выделить память и скопировать байты. Обычный срез часто — именно то, что нужно: если подстрока живёт несколько микросекунд внутри функции, копия ради отвязки не нужна.
Копируй, когда одновременно верны два условия:
- исходная строка очень большая;
- маленькая подстрока живёт значительно дольше исходника (кеш, глобальная структура).
Вы узнаете:
- как та же логика удержания работает у слайсов
- как отвязывали строку до появления
strings.Clone
Проверьте себя
Аня просит сформулировать правило копирования одним предложением. Какой вариант верный?
Из файла на 500 МБ извлекается 20-байтный ID и сохраняется в кеш на месяцы. Нужен ли strings.Clone?
Что делает strings.Clone(data[:100])?
правильных ответов
Подстрока держит весь backing storage исходника живым, пока жив её заголовок. Дешёвый срез хорош для короткоживущих данных; когда маленький результат должен жить долго, а исходник огромный — отвяжи его strings.Clone.
10 000 ₽ · 60 минут · онлайн