TGViewer
Daria’s room Daria’s room @dariasroom · 1.2K subscribers
Post #90 1.04K
Part 1: Hash Array Mapped Trie (HAMT)

Дурость с деревьями продолжается. После недели разговором с von Pavlo о прочих особенностях префиксных деревьев, попробовали переписать индексатор для поискового движка с Radix Trie на HAMT - и не ошиблись.

HAMT (Hash Array Mapped Trie) — это дерево для хранения ключей и чего захотите, которое сочетает в себе hash table и trie, где каждое слово(ключ) хешируется, а части от этого хеша используются, чтобы определить путь в дереве.

То есть навигация к нужному ключу определяется через биты его хеша, а не через префиксы.

Итак, само HAMT разбито на узлы по уровням, каждый уровень отвечает за часть битов хеша:
- Уровни с узлом Node, которые отвечают чисто за ветвление по дереву.
- Уровень с узлом Terminal, который всегда последний и хранит сами данные.

Собственно, само Trie будет содержать отдельно слайд нод и терминалов.


bitmap uint32 // определяет, какие из 32 слотов заняты
children []nodeptr // плотный (dense) массив указателей на Node или Terminal, где индекс элемента соответствует номеру включенного бита
}

Для этой реализации был выбран обычным strhash32 как самый компромисс между 8 и 64 (с разбегом на 32 для 650к види документов не должно быть много коллизий)

type Terminal struct {
Entries []entry // массив cо списками документов по каждому слову
}


Итак, c группами по 5 бит мы получаем 32 / 5 = 6.2 = 7 уровней, где 1-6 уровень состоит из Node, которые хранят bitmap и указатели на дочерние узлы, а последний 7 уровень идет с Terminal. Терминальная нода не участвует в навигации по битам хеша, а хранит реальные данные.


type entry struct {
key string
docs []fts.Documents
}


Terminal содержит массив entries, где каждый элемент хранит слово и соответствующие документы с количеством вхождений. Это решение далось тяжело, но внутри Terminal не учитываются последние 2 бита хеша - то есть один список может включать как слова, различающиеся именно по этим битам, так и коллизии, (когда разные слова попали в один слот хеша).

Да, такой общий список может быть достаточно большим, но ниже я поясню как эту проблему решает упорядочиванием слайса и использованием бин поиска, чтобы поиск по такому слайсу оставался норм.

Ну и самое главное: в таком дереве глубина константная, что делает его ну прямо очень выйгрышным вариантом для текстового поиска т.к. позволяет быстро находить слоты и заранее знать, сколько уровней нужно пройти. Собственно, это и было причиной попробовать.


Как происходит поиск следующей ноды в HAMT:

На каждом уровне ищем следующую ноду. Собственно, как это происходит:
1. Берём через побитовое И младшие 5 бит хеша:

slot := hash & lowerbits


результат (slot) все еще uint32, но c включенными битами только в диапазоне последних 5 бит (а значит там будут значения от 0 до 31)

2. Cдвигаем uint32(1) на slot позиций - получаем маску с включенным битом на нужной позиции:

mask := 1 << (hash & lowerbits) 


3. Побитовым И проверяем, есть ли в битмапе включенный бит на нужной нам позиции:


if node.bitmap & mask == 0 {
return n, false // слота нет
}


4. Считаем все занятые слоты до нашего индекс в children:

index := bits.OnesCount32(node.bitmap & (mask - 1))


Имея нужных индекс, повторяем с child[index] на каждом последующем уровне, сдвигая хеш на 5 бит:

hash >>= 5


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

#fts #projects #go
Telegram Чайник из Юты Прочие вкусные разновидности деревьев Я не договорил. Judy array Как я и говорил, если дерево не устраивает - его нужно привить с другим, чтобы устроило. Селекция, ёпта. А Judy array - это как раз буквально такая ядерная смесь: — Обычное 256-арное дерево…
  • 👍 8
  • ❤ 4
  • 🔥 2
More from @dariasroom
  1. Sep 15, 2026Автоматическое сжатие на клиенте vs ручное на сервере Стандартный клиент http.Transport са…
  2. Sep 5, 2026Причина перекосов в уровне балансировки L4-балансировщик выбирает серверную ноду при созда…
  3. Aug 28, 2026Итераторы… TL;DR: в iter.Seq итератор сам передаёт следующие элементы в код внутри range,…
  4. Aug 17, 2026Что на самом деле нужно сохранять при сериализации сложной структуры? TL;DR: Важно отделит…
  5. Aug 13, 2026Привет! Вас стало больше, так что пора наконец представиться 🙂 Я Даша, давно пишу на Go,…
  6. Aug 12, 2026HNSW: как устроен графовый индекс для векторного поиска One million years later, я наконец…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →