Освобождает ли ключ из мапы в Go память
Что проверяют
Понимаете ли вы разницу между «элемент удалён» и «память возвращена». Вопрос с подвохом: по поведению len кажется, что мапа уменьшилась, а по потреблению памяти процесс говорит обратное. Заодно проверяют, знаете ли вы, как устроено хранилище мапы.
Как отвечать
Первое: delete(m, k) действительно убирает пару. Последующее чтение вернёт нулевое значение и ok равный false, len уменьшится. Рантайм зачищает слот, поэтому указатели, лежавшие в ключе и значении, перестают удерживать объекты, и сборщик мусора заберёт их, если других ссылок нет.
Второе и главное: сама мапа выделенные бакеты обратно не отдаёт. Она остаётся размером под тот максимум, который в ней когда-либо был, и после удаления всех ключей занятая память не уменьшается. Поэтому разово раздувшийся до миллионов записей кэш держит память до конца жизни процесса.
Лечится это пересозданием: собрать остаток в новую мапу, а старую отпустить целиком.
fresh := make(map[string]*Entry, len(cache))
for k, v := range cache {
fresh[k] = v
}
cache = freshВторой приём это шардирование по времени: раскладывать данные по нескольким мапам и выбрасывать устаревшую целиком. И если значение крупная структура, храните указатель: тогда после удаления освободится тело, а в бакете останется одно слово.
Где спотыкаются
Отвечают «да, освободилась», опираясь на len. Он считает живые пары, а не занятые байты. Проверять надо профилем кучи или статистикой рантайма, и там видно, что после массового удаления картина не меняется.
Повторяют миф, будто ключ остаётся в мапе до её уничтожения. Пара удаляется и по ключу больше не находится. Остаётся выделенная под бакеты память, а это совсем другое утверждение, и путаница между ними как раз и стоит балла на собеседовании.
Боятся удалять во время обхода. В Go это разрешено: удалённый и ещё не посещённый ключ просто не будет выдан. А вот добавление во время обхода не определено, новый ключ может попасться, а может нет, и такой код лучше не писать.