Ответ
Это фундаментальное проектное решение в реализации map
в Go, направленное на достижение баланса между производительностью и потреблением памяти.
Ключевые причины:
Эффективность работы с кэшем CPU. Современные процессоры загружают данные из памяти в кэш не по одному байту, а целыми блоками — кэш-линиями (обычно 64 байта). Размер бакета и его внутренняя структура спроектированы так, чтобы умещаться в небольшое количество кэш-линий. Это минимизирует «промахи» кэша и значительно ускоряет доступ к данным, так как связанные данные (ключи, значения, хеши) находятся в памяти рядом.
Снижение накладных расходов на память. Если бы бакет был очень маленьким (например, 1-2 элемента), то для хранения большого количества элементов потребовалось бы огромное количество самих бакетов и указателей на них, что увеличило бы потребление памяти.
Управление коллизиями. Когда в бакет нужно добавить 9-й элемент, Go не расширяет сам бакет. Вместо этого он создает дополнительный overflow-бакет и связывает его с основным. Хранение 8 элементов в одном бакете является эмпирически подобранной константой, которая обеспечивает низкую вероятность переполнения для большинства сценариев использования.
Структура бакета в исходном коде Go выглядит примерно так:
// A bucket for a Go map.
type bmap struct {
// tophash содержит последние 8 бит хешей для каждого ключа в бакете.
// Помогает быстро найти нужный ключ без полного сравнения.
tophash [8]uint8
keys [8]keyType
values [8]valueType
// Указатель на следующий overflow-бакет, если этот переполнен.
overflow uintptr
}
Таким образом, число 8 — это компромисс, который хорошо зарекомендовал себя на практике, обеспечивая быстрый доступ и эффективное использование памяти.