Почему в бакете (bucket) стандартной map в Go хранится не более 8 элементов?

Ответ

В текущей реализации map в Go каждый бакет (bucket) действительно может напрямую хранить до 8 пар ключ-значение. Это значение (bucketCnt = 8) — константа, выбранная разработчиками языка как эмпирический компромисс.

Почему именно 8?

Это баланс между скоростью доступа и использованием памяти:

  • Если бы число было меньше (например, 4): Для хранения того же количества элементов потребовалось бы больше бакетов. Это увеличило бы расход памяти на метаданные и указатели, а также вероятность коллизий, требующих создания цепочек бакетов.
  • Если бы число было больше (например, 16): Поиск элемента внутри одного бакета (который является линейным) стал бы медленнее. Кроме того, для маленьких map много памяти внутри бакетов оставалось бы неиспользованной.

Число 8 обеспечивает хорошую производительность для большинства сценариев использования.

Что происходит при заполнении бакета?

Когда в бакет нужно добавить 9-й элемент (из-за коллизии хешей), происходит следующее:


  1. Переполняющиеся бакеты (Overflow Buckets): Go не увеличивает размер самого бакета. Вместо этого он выделяет дополнительный "overflow" бакет и связывает его с основным, образуя связанный список бакетов. Поиск будет продолжаться по этой цепочке.



  2. Рост карты (Map Growth / Rehashing): Важно понимать, что рост map (выделение вдвое большего массива бакетов и перенос всех элементов) происходит не когда один конкретный бакет заполнился, а когда коэффициент загрузки (load factor) превышает определенное значение (около 6.5). Это означает, что в среднем на каждый бакет приходится 6.5 элементов (включая overflow-бакеты). Заполнение одного бакета — это лишь симптом, который способствует увеличению этого коэффициента.


Структура бакета (упрощенно):

// go/src/runtime/map.go
type bmap struct {
    // tophash хранит старшие байты хешей для быстрого поиска
    tophash  [8]uint8 
    // далее идут непрерывные массивы ключей и значений
    // keys     [8]keytype
    // values   [8]valuetype
    // overflow указывает на следующий бакет в цепочке
    overflow *bmap 
}