Skip to content

Release v0.4.0: clustering 3.7× less CPU on the many-small-groups shape, bit-identical - #3

Merged
prostomarkeloff merged 1 commit into
mainfrom
perf/cluster-3x
Sep 4, 2026
Merged

prostomarkeloff merged 1 commit into
mainfrom
perf/cluster-3x

Conversation

@prostomarkeloff

Copy link
Copy Markdown
Owner

TL;DR

cluster_canonicals в форме, которую реально выдаёт find-dup-defs (тысячи вызовов по 2–3 длинных канонических тела, несколько по сотням), стал в 3.7× дешевле по CPU (9.78 s → 2.66 s на одном потоке) и в 3.4× по wall (1.30 s → 0.38 s на 12 потоках) на реплее всех 2 892 вызовов одного прогона. Вывод — побайтово тот же: реплей сверен по кластерам и битам min_sim с бинарём 0.3.5 на трёх дампах, find-dup-defs прогнан в десяти режимах на двух корпусах против своего baseline.

Что было

  • Автомат строился для каждой строки, хотя пара сканирует lo по автомату hi: в группе из двух строк половина билдов не нужна, а билд — самая дорогая операция на строку.
  • Проверялись все пары-кандидаты как рёбра (104k), из них 60 % — reject'ы после полного скана и глубокой рекурсии; при этом single-linkage'у нужны только компоненты.
  • min_sim считал внутрикластерные пары под cap'ом, но cap был локальным на задачу и стартовал с 1.0.
  • Половина окон рекурсии — с b-стороной в несколько символов; обход цепочки суффиксных ссылок там почти всегда ничего не находил.
  • Merge-sort-дерево на все позиции (по аллокации на лист) отвечало за <1 % запросов.

Что стало

  • Ленивые автоматы (LazySams): строятся только для hi-сторон пар, дошедших до скана.
  • Только остовные рёбра (spanning_edges): кандидаты идут батчами от самых похожих (по quick_ratio), между батчами union-find, уже связанная пара не тестируется. Тестов рёбер 104k → 32k.
  • Общий cap в assemble: все внутрикластерные пары без кэшированного ratio считаются под атомарным минимумом кластера; сначала пара с наименьшей верхней оценкой сидирует cap. Точность: пара-минимум всегда ≤ любого cap'а, который ей достаётся, и считается точно.
  • Ранние выходы до рекурсии: общий префикс/суффикс и первый блок скана длиной ≥ порога доказывают M ≥ need / ratio > cap.
  • longest_direct для окон с wb ≤ 8 (16 и 64 — медленнее), max-heap по размеру окна в рекурсиях с ранним выходом.
  • Билдер: thread-local с четырьмя inline-переходами на состояние, endpos-диапазоны двумя проходами по длине вместо DFS, сортированные копии только для больших endpos-множеств (radix), 32-байтный слот скана с inline-переходом вдоль b.

Что измерено и не дало: DP-оценка сверху по fmatch как reject внутри скана (на коде не срабатывает), уточнение bound'ов по позициям вниз по рекурсии, чередование 4 сканов (скан instruction-bound: +30 % хуже), 128-битное множество символов в слоте.

Что изменилось

src/gestalt.rs

Sam без node/merge-sort-tree: edges, root_next, fast: Vec<ScanSlot> (32 B, align 32), pos_state, epos, chain_slot (32 B), big_sorted. Builder с inl: Vec<[u32; 8]> + overflow-арена. matching_stats_bounded (in-scan accept), common_ends, prefix_fill, longest_direct, Window + HEAP_BUF, wlen-параметр longest_in, gestalt_edge_bounded, capped_recursion.

Breaking: gestalt_qualifies_ms(a, b, sam_b, …) вместо (na, nb, …); Sam::nodes() возвращает Vec<[u32; 4]>.

src/lib.rs

cluster_canonicals_chars = char_countscandidate_pairsspanning_edgesassemble; assemble(n, edges: Vec<(usize, usize, Option<f64>)>, chars, &LazySams); SERIAL_BELOW, EDGE_BATCH_MAX; ASCII-гистограмма в char_counts.

src/rationer.rs, src/gpu.rs

GPU-пути отдают assemble те же Option<f64> (Some) и LazySams::built; CorpusGpu::build читает nodes() как Vec.

Проверка

что результат
реплей 2 892 вызовов (mono, lenses), 1 поток 9 780 → 2 657 ms
реплей, 12 потоков 1 299 → ~380 ms
реплей mono default (3 440 вызовов) 1 061 → ~325 ms
реплей mixed lenses (305 вызовов) 67 → 25 ms
parity реплея (кластеры + биты min_sim) на трёх дампах побайтово
find-dup-defs, 10 режимов × {mixed, mono} против baseline побайтово
find-dup-defs mono --kinds lenses, фаза pass1-name / весь прогон (hyperfine ×5) 1 472 → 546 ms / 6.79 → 5.84 s (1.16×)
find-dup-defs mono default, фаза pass1-name / весь прогон 1 158 → 504 ms / 4.65 → 4.00 s (1.16×)
cargo test --release (+ --features gpu) ok
cargo clippy --release --all-targets (+ --features gpu, instrument) чисто

…al (0.4.0)

cluster_canonicals in the shape find-dup-defs issues it — thousands of calls per
repository, most of two or three long canonical bodies, a few of hundreds — replayed
call for call through the library: 9.78 s -> 2.66 s single-threaded, 1.30 s -> 0.38 s
on 12 threads, every cluster and every min_sim bit identical to 0.3.5; the tool's ten
output modes diffed identical on two corpora.

- Automata are built lazily, only for the strings that are actually scanned
  (a pair scans one side against the other's automaton).
- Only spanning edges are tested: candidates go most-similar-first in batches over a
  union-find, and a pair already connected is not tested. Edge tests 104k -> 32k.
- The cluster minimum runs every uncached intra pair under the cluster's shared
  running minimum; a common prefix/suffix or a single long block decides a pair
  before (or during) the scan; the minimum pair is always computed exactly.
- Narrow recursion windows (b side <= 8) use a direct row comparison instead of the
  chain walk; the early-exit recursions take the largest window first.
- Builder: per-thread, four inline transitions per state, endpos ranges by two
  length-order passes instead of a tree walk, sorted copies only for the few large
  endpos sets (radix) instead of a merge-sort tree, a 32-byte scan slot whose inline
  transition continues along b.

Breaking: gestalt_qualifies_ms takes (a, b, ..) instead of lengths; Sam::nodes()
returns a Vec.
@prostomarkeloff
prostomarkeloff merged commit 1d327cf into main Sep 4, 2026
8 checks passed
@prostomarkeloff
prostomarkeloff deleted the perf/cluster-3x branch September 4, 2026 20:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant