Files
2026-08-13 23:42:26 +03:00

139 lines
9.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Архитектура
## Представления
`AdaptiveSequence<T>` хранит `variant<vector<T>, TieredStorage<T>>`.
Contiguous vector выгоден для малых последовательностей и индексного чтения.
Tiered storage состоит из независимо выделенных циклических leaf-блоков
(`RingBlock`) и многоуровневого каталога весов.
У обоих backend есть единая **логическая вместимость** `capacity()`. Это не
сумма физического slack в leaf-блоках, а управляющая граница, на которой
разрешена автоматическая O(N)-перестройка.
Текущий `TieredStorage` остаётся сегментированным исследовательским вариантом, а
не полной реализацией implicit tiered vector с offsets/carry на каждом
внутреннем узле. Offsets используются внутри leaf, но split/merge может менять
массив leaf-дескрипторов и перестраивать каталог за O(number_of_leaves). Это
ограничение нужно учитывать при интерпретации результатов.
## Текущая default-policy: перестройка только с capacity
`ResizePolicy` не пытается распознать workload после каждой операции. В
auto-mode представление и геометрия пересматриваются только вместе с изменением
логической вместимости:
1. Перед вставкой, которая не помещается, capacity удваивается до достаточного
значения. Для обычного роста это одна граница `C -> 2C`.
2. После удаления при `size <= capacity / 8`, то есть при заполнении не более
12.5%, capacity уменьшается на один шаг `C -> C/2` (но не ниже `size`).
3. Внутри той же перестройки выбираются mode, leaf geometry и размер hash
table. Между такими границами они остаются неизменными.
В auto-mode целевое представление определяется размером на момент перестройки:
```text
target_size < 4096 -> vector
target_size >= 4096 -> tiered
```
`4096` — cutoff, подтверждённый текущим focused-run 7 x 100000 для
`AdaptiveSequence<uint32_t, true>` и заданного mix; это не универсальный
результат для всех типов и workload. Само пересечение `size=4096` немедленного перехода не
вызывает: mode меняется только на следующей capacity-boundary. Например, при
обычном последовательном росте контейнер остаётся vector при capacity 4096 и
переходит в tiered, когда следующая вставка меняет capacity на 8192. На shrink
boundary правило применяется заново, поэтому возможен обратный переход
`tiered -> vector`.
Явные `force_vector_mode()`, `force_tiered_mode()` и `reserve()` остаются
пользовательскими управляющими операциями и не являются автоматической
адаптацией.
## Геометрия tiered storage
При capacity-boundary размер малого массива вычисляется из фактического размера
последовательности на этой границе:
```text
leaf_capacity = max(4, ceil(sqrt(target_size)))
```
Значения fanout и допустимой глубины каталога берутся из `TieredConfig`.
`leaf_capacity` не подстраивается после каждой вставки или удаления: новое
`sqrt(N)` применяется только в общей перестройке. Поэтому rebuild одновременно
может выполнить `vector -> tiered`, `tiered -> vector` или
`tiered -> tiered` с новой геометрией.
## Flat hash index
`AdaptiveSequence<T, true>` использует open-addressed flat table
`value -> {head_id,count}`. Дубликаты связаны через компактные intrusive links в
ID metadata. `find_one` возвращает любой экземпляр, `find_all_ids` — unordered
stable IDs, а `find_all` сортирует логические позиции и может стоить
O(k log k).
Внутри цепочек используются 32-битные slot IDs. Освобождённые metadata slots
переиспользуются через free list, а публичный 64-битный stable ID кодирует
`generation + slot`. Поэтому сбалансированный insert/erase churn не наращивает
metadata без границ и старый ID не оживает после повторного использования slot.
Hash table следует той же capacity-policy, что и storage:
```text
required_buckets = ceil(logical_capacity / 0.70)
bucket_count = next_power_of_two(max(16, required_buckets))
```
Иными словами, таблица заранее рассчитана на худший случай «один уникальный
ключ на элемент» при load factor не выше 70%. При росте bucket reserve/rehash
выполняется перед перестройкой storage, при shrink — после неё. Целевое число
buckets пересчитывается только при изменении логической capacity; если оно не
изменилось, отдельного rehash нет. Обычные вставки и удаления между границами не
запускают независимое увеличение hash table.
Такой выбор сохраняет O(1) expected lookup и включает стоимость редкого rehash
в ту же измеряемую capacity-перестройку, где уже оплачивается перенос storage.
Flat hash остаётся compile-time optional: вариант `AdaptiveSequence<T, false>`
не несёт его память; тот же API поиска по значению работает линейным обходом.
## Инвалидация
- const-чтение и hash lookup ничего не инвалидируют;
- `set()` не меняет логические позиции, но ссылка на заменённое значение не
должна использоваться как ссылка на старый объект;
- вставка или удаление инвалидирует позиционные references, pointers и
iterators согласно структурному изменению; capacity-boundary дополнительно
может перестроить весь backend;
- conversion, geometry rebuild, `force_*`, `reserve`, `clear` и
`make_contiguous()` инвалидируют всё позиционное;
- stable ID переживает shifts, split и смену представления и перестаёт быть
живым только после удаления соответствующего элемента или `clear()`.
Stable ID имеет область действия конкретного состояния конкретного контейнера.
Copy/move assignment заменяет это состояние: ранее полученные ID обеих сторон
после assignment использовать нельзя (их числовое значение может совпасть с ID
элемента нового состояния). Move construction переносит состояние целиком и
сохраняет его ID в новом объекте.
Debug iterator хранит structural generation и бросает `logic_error` после
инвалидирования.
## Historical / retired: operation-window CostModelPolicy
До текущей постановки default-policy собирала `OperationSample`, оценивала
vector и набор фиксированных leaf 64/128/256/512/1024, применяла EWMA,
hysteresis и прогноз окупаемости O(N)-перехода:
```text
(current_cost - candidate_cost) * forecast
> rebuild_cost * safety_factor
```
Также исследовались deferred/eager read adaptation, minimum residency и
отдельные `vector -> tiered`, `tiered -> vector`, `tiered -> tiered` решения.
Эта схема и соответствующая benchmark-матрица **retired**: они сохранены для
воспроизводимости старых CSV и для экспериментов с явно выбранной
`CostModelPolicy`, но не описывают текущую default-policy и не используются для
нового cutoff.