New adaptation policy

This commit is contained in:
Efim Beshmenev
2026-08-13 23:42:26 +03:00
parent bbb43dbe57
commit 3770224eaf
16 changed files with 2533 additions and 489 deletions
+113 -75
View File
@@ -1,99 +1,137 @@
# Сборка и benchmark-методика
## Профили кода
## Активная focused-постановка
Все сравниваемые бинарники — Release x64 `/O2`, `/MT`, без IPO/LTO. Отличается
только ось SIMD:
Текущий benchmark отвечает на один практический вопрос: при каком `N` для
фиксированной малой доли правок становится выгодно переходить от contiguous
vector к tiered storage. Старую широкую матрицу типов, SIMD-профилей, leaf и
workload для этой калибровки не используют.
| Профиль | Ключи проекта | Назначение |
Все три кандидата имеют один и тот же тип данных и один и тот же flat hash:
| Имя в CSV | Реализация | Режим |
|---|---|---|
| `scalar` | `/O2 /d2Qvec- /Qvec-report:1 /Oi-` | MSVC-specific `project-novec` |
| `baseline` | `/O2`, без `/arch:AVX*` | обычный MSVC x64 |
| `avx2` | `/O2 /arch:AVX2` | разрешён AVX2 |
| `forced_vector_hash` | `AdaptiveSequence<uint32_t, true>` | принудительно vector |
| `forced_tiered_hash` | `AdaptiveSequence<uint32_t, true>` | принудительно tiered |
| `adaptive_hash` | `AdaptiveSequence<uint32_t, true>` | текущая resize-bound auto-policy |
`/d2Qvec-` — внутренний, недокументированный ключ MSVC, а не переносимая
гарантия. Командная строка проверена автоматическим аудитом для MSVC 19.51;
вывод `/Qvec-report` пока не разбирается как отдельное доказательство. `scalar`
не означает «ни одной SIMD-инструкции в процессе»: x64 ABI имеет SSE2-baseline,
а CRT `memmove` может runtime-dispatch-ить SIMD. Дополнительно задаётся
`_USE_STD_VECTOR_ALGORITHMS=0`, чтобы STL не включала свои явно
векторизованные алгоритмы. Это честнее называть `project-novec`.
Таким образом, поиск по значению не даёт одному кандидату скрытого преимущества:
стоимость хранения и обновления hash index присутствует у vector, tiered и
adaptive. Для forced tiered начальный leaf равен `ceil(sqrt(N))`. Auto-mode
выбирает mode и leaf только при capacity-boundary.
## Матрица
## Трасса
Benchmark сравнивает:
Mixed trace содержит ровно 100000 операций в default-запуске:
- reserved `std::vector`;
- `std::deque`;
- `std::list` с index semantics;
- forced tiered для каждого leaf 64/128/256/512/1024;
- adaptive deferred;
- experimental adaptive eager-read.
| Операция | Доля | Default count | Детали |
|---|---:|---:|---|
| индексное чтение | 97% | 97000 | случайный существующий индекс |
| hash find по значению | 1% | 1000 | `find_one`, 50% hit / 50% miss |
| вставка | 1% | 1000 | случайная позиция |
| удаление | 1% | 1000 | случайная существующая позиция |
`std::deque` и `std::list` — только внешние фиксированные baselines;
`AdaptiveSequence` никогда в них не переключается. Для `std::list` runner
эмулирует index semantics линейным проходом, поэтому доступ по индексу имеет
O(N).
Виды операций перемешиваются детерминированным seed. Вставки и удаления
сбалансированы, поэтому benchmark измеряет steady-size workload, но любая
реально достигнутая capacity-boundary и стоимость перехода остаются внутри
mixed timer. Построение исходного контейнера находится вне timer.
Типы: `uint32_t`, `uint64_t`, trivially-copyable 16/32/64-byte значения и
non-trivial movable string wrapper. Workloads включают random access, traversal,
append, 99.99/99/80/50% reads, uniform/localized edits, bursty edits и отдельный
phase trace:
Кроме общей `mixed ops/s`, runner измеряет отдельные homogeneous batches и
выводит `read ops/s`, `find ops/s`, `insert ops/s`, `erase ops/s`. Быстрые read
и find batches содержат не менее 100000 вызовов для устойчивости таймера;
фактические batch counts записываются в CSV. Поэтому отдельные колонки — это
пропускная способность соответствующего типа операции, а не время его 1%-доли
в mixed trace.
```text
uniform edits -> maintenance
localized edits -> maintenance
reads -> maintenance
Один и тот же материализованный trace и seed применяются к трём кандидатам.
Порядок кандидатов циклически меняется между paired repeats, чтобы фиксированная
первая или последняя позиция не принадлежала всегда одному представлению.
Checksum проверяет совпадение состояния и результатов операций.
## Capacity, leaf и hash во время измерения
Focused benchmark проверяет текущий контракт контейнера, а не внешнюю
эмуляцию:
- capacity увеличивается `x2`, когда очередная вставка не помещается;
- при `size <= capacity / 8` выполняется один shrink-шаг `capacity / 2`;
- mode и `leaf = ceil(sqrt(size))` пересчитываются только внутри этой
capacity-перестройки;
- focused cutoff auto-mode равен 4096;
- hash buckets рассчитываются из новой логической capacity с worst-case load
не выше 70% и не меняются независимо между capacity-boundaries.
Raw CSV сохраняет initial/final mode, leaf и logical capacity mixed-контейнера,
что позволяет увидеть включённый в mixed throughput переход `vector -> tiered`
или обратный переход. Для четырёх отдельных homogeneous batches поля
`read_mode`, `find_mode`, `insert_mode`, `erase_mode` и соответствующие
`*_leaf` отдельно фиксируют их фактический режим/геометрию; эти batches
стартуют с новых контейнеров и потому не обязаны повторять final mode/leaf
mixed-трассы.
## Воспроизводимый default-run
Из обычной командной строки Windows:
```bat
benchmark.bat
```
Последний trace нужен именно для проверки смены leaf, а не только
`vector↔tiered`. Время rebuild остаётся внутри измеряемого end-to-end интервала.
Скрипт выполняет ровно один focused-run:
Tuning перебирает leaf 64…1024 и maximum directory levels 2/3/4. Каталог сам
останавливает построение на минимально достаточной глубине.
1. `build.bat baseline test` — Release x64 `/O2`, `/MT`, без `/arch:AVX*` и
без IPO/LTO, затем correctness-тесты.
2. Создаёт `results\benchmarks\<timestamp>-focused\` и записывает metadata и
source/binary manifests.
3. Запускает `uc_focused_bench` для степеней двойки `N=256...65536`, 100000
mixed operations и 7 paired repeats.
4. Печатает таблицу медиан operations/second, сохраняет её в `summary.md`, а
raw repeat data — в `baseline-focused.csv`.
Эквивалентный вызов runner после сборки:
```bat
out\bin\baseline\Release\uc_focused_bench.exe --min-n 256 --max-n 65536 --operations 100000 --repeats 7 --output results\benchmarks\focused.csv
```
`--operations` должен быть положительным числом, кратным 100. Доступны также
`--seed`, `--min-n` и `--max-n`; границы диапазона округляются до степеней
двойки.
Финальную таблицу следует публиковать в каталоге соответствующего запуска в
`results/benchmarks/`. Документация хранит только постановку и подтверждённые
выводы, а не копию ещё незавершённой серии.
## CSV
Raw CSV содержит profile/type/workload/N/seed, initial и final tier config,
total/ns-per-op, p50/p95/p99/max edit latency, checksum, allocated bytes,
storage switches, shape rebuilds и final mode. Checksum должен совпасть у всех
контейнеров одной трассы.
Каждая raw-строка содержит:
`tools/analyze_results.ps1` сначала берёт median повторов, затем сравнивает с
лучшим фиксированным baseline в каждой ячейке. Итоговый geometric mean считается
иерархически по sizes → workloads → types → profiles, чтобы семейство с большим
числом строк не получило лишний вес.
- `profile`, `n`, `container`, `repeat`, `seed`;
- initial/final mixed `mode`, `leaf`, `logical_capacity` и отдельные mode/leaf
поля read/find/insert/erase batches;
- mixed counts, включая hit/miss hash find;
- реальные размеры отдельных read/find/insert/erase batches;
- `mixed_ops_per_sec`, `read_ops_per_sec`, `find_ops_per_sec`,
`insert_ops_per_sec`, `erase_ops_per_sec`;
- оценку allocated bytes и checksum.
## Масштабы запуска
Публикуемая таблица использует медиану семи повторов по каждой паре
`(N, container)`. Значения отдельных повторов не отбрасываются и остаются в
raw CSV.
```bat
benchmark.bat smoke rem проверка всего конвейера
benchmark.bat quick rem рабочее исследование
benchmark.bat full rem финальные повторы, длительный запуск
```
## Historical / retired benchmark matrix
Отдельная large-матрица с обязательным reserved vector, fixed leaf 64…1024 и
adaptive запускается внешним process pool. Второй аргумент — число workers:
Старый `uc_bench`, вызовы `benchmark.bat smoke|quick|full`, multi-profile
сравнение `scalar/baseline/avx2`, типы от `uint32_t` до blob/string wrapper,
fixed leaf 64...1024, `std::deque`, `std::list`, phase traces и
`large_benchmark.bat` до N=100M относятся к **historical / retired** методике.
```bat
large_benchmark.bat smoke 12
large_benchmark.bat full 12 3
large_benchmark.bat full 12 3 huge
```
Их CSV сохранены для аудита истории проекта, но не участвуют в выборе текущего
cutoff: набор кандидатов, mix операций и наличие hash index отличаются от
focused постановки. Старые aggregate scores и speedup нельзя смешивать с новой
таблицей operations/second.
Каждый worker закрепляется за отдельным logical CPU. Третий аргумент ограничивает
число одновременно работающих N=100M-ячеек и лишь снижает риск pagefile; это
эвристика, а не измеритель доступной памяти. Четвёртый аргумент `huge` оставляет
только N=100M. Large raw,
per-cell partial/log, план, environment и сводки сохраняются в отдельной папке
`results/benchmarks/`.
`large_benchmark.bat full 12 3 huge` воспроизводит только N=100M с удлинённым
512-edit horizon и сам проверяет поддержку AVX2. Прямой PowerShell runner такой
CPU-проверки не выполняет: при его ручном запуске `avx2` надо исключить на
неподдерживаемой машине.
Для публикации финального числа нужны одинаковый power plan, отсутствие тяжёлой
фоновой нагрузки, закрепление на физическом ядре, randomized paired order и
holdout workloads/seeds. Текущие quick-данные являются calibration set, а не
финальным доказательством.
Профили `scalar` и `avx2` по-прежнему можно собирать для отдельных инженерных
экспериментов. `scalar` использует внутренний MSVC `/d2Qvec-` и не является
универсальной гарантией отсутствия SIMD в CRT/ABI; это ещё одна причина не
расширять текущий baseline-run до старой матрицы без отдельной задачи.