perf(rust): make the uuid_on_collision merge pass near-linear - #143
Merged
Conversation
… merge-state guard, error-mode/node bench scenarios
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Advanced Run ID: Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
SkyeAv
added a commit
that referenced
this pull request
Sep 8, 2026
Cut 17.0.0 and bump the package version in pyproject.toml, uv.lock, and CITATION.cff. Major: the parallelism surface is removed (#142) - the `--threads` / `-t` flags on `build-kg` and `build-fullmap`, the agent command's `--gepa-threads` flag, and the `threads` / `num_threads` parameters on the Python and Rust pyfunctions are gone; migration is to drop those arguments, since the automatic behavior is exactly the previous default (all CPU threads, fullmap builds memory-capped on Linux, lookups parallelized for batches of 1024+ terms) and results are unchanged. Also shipping: the `uuid_on_collision: merge` fold is near-linear and the canonical serializer allocation-free (#143) - up to 28.6x on merge-heavy workloads and 14.6% faster in default `error` mode, with byte-identical output. Changelog: - Versioned the Unreleased section as 17.0.0, renamed its `### Removed` subsection to `### Breaking Changes`, gave the threads-removal entry its PR link and a migration note, and moved the #143 Performance entry out of the already-released 16.6.2 section, where it had been misfiled, into 17.0.0. Docs: docs/cli.md, docs/fullmap.md, docs/api/fullmap.md, docs/agent.md, and examples/agent/README.md were updated by #142 itself; none needed here - the release commit touches version metadata and CHANGELOG.md only. Testing: - uv run pytest -q -> 1243 passed, 3 skipped (96% coverage) - uv run ruff check . && uv run ruff format --check . && uv run pyright -> clean / 0 errors - uv lock --check -> up to date - uv run mkdocs build --strict -> clean - cargo fmt --check && cargo clippy --all-targets -- -D warnings -> clean (rust/ touched)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The
uuid_on_collision: mergefold re-canonicalized and re-sorted every already-stored list item on each incoming row — quadratic in divergent rows per edge — which made merge-mode graphs a build bottleneck: 37.5 s for a 100k-line workload, >98% of it in the fold. This makes the fold near-linear and the canonical serializer allocation-free, with byte-identical output proven against frozen copies of both old implementations: the worst-case shape is 28.6x faster and the defaulterrormode is 14.6% faster.Merge fold (
rust/src/ndjson.rs)merge_recordsre-canonicalized all stored list items, linear-scannedVec::containsmembership, and re-sorted the whole array on every fold. Each array field now carries aListState— membershipFxHashSet<SharedBytes>, parallel orderedVec<SharedBytes>,unionedflag: canonical bytes are computed once when an item arrives, every fold is an O(1) membership check per incoming item, and a single deferred stable sort runs at write-out only for fields that actually went through a union (copied-never-unioned lists keep source order).SharedBytesisRc<[u8]>shared between the set and the vec —Hash/Eqdelegate to the full canonical bytes, never a bare hash (matching therecord_if_newprecedent). Peak RSS on the scenario-C shape: 1931 MB → 1256 MB (−35%).MergeIndex::absorbcontent-hash membership is now anFxHashSet<u64>; exact byte repeats are still suppressed without incrementingmergedor re-running the fold.number_of_casesrecompute with its −1 conflict unwind, first-wins scalars, id-skip,supporting_case_idscarrier strip (including serde_jsonpreserve_order's swap_remove key ordering), and fail-loudly malformed-JSON handling.MergedRecord::finishraises a structuredmerge-state-desyncruntime_errorinstead of silently truncating if the bytes/items parallel invariant ever breaks; pinned by a negative test.Canonical serialization (
rust/src/json.rs)canonical_json_byteswrites sorted-key JSON straight into the output buffer with the same key comparator; scalars delegate toserde_json::to_writer, so escaping and number formatting are byte-identical by construction. The deepValueclone is gone from production.error-mode edge content hash flows through this serializer — 598k-line error-mode workload: ~3.54 s → ~3.03 s (−14.6%). The node path never touches it (finalize_recordearly-returns for!is_edges;record_if_newkeys onemitted_json_bytes) and measured flat (−1.7%) as a regression guard.Equivalence proof
#[cfg(test)]copies of the old fold (merge_records_reference/MergeIndexReference) and old serializer (canonical_json_bytes_reference) are the differential targets.merge_fold_matches_reference_on_fuzzandcanonical_json_bytes_matches_reference_on_fuzzdrive old-vs-new on reproducible randomized streams — divergent groups, object/scalar list items, key-order variants, late fields including copied-never-unioned lists, scalar-vs-array conflicts, exact repeats, unicode/exponent number forms — asserting byte-identical records, first-seen order, and(merged, scalar_conflicts)counters.tests/test_rs.py::test_dedup_edges_merge_matches_python_referencere-implements the semantics in pure Python and asserts byte-identicalrs.dedup_ndjsonoutput with matching counters.merge_fold_speedup_bound_vs_referencetimes the new fold against the frozen quadratic reference in-process and fails below 5x (measured 21–31x) — machine-independent; no absolute wall-clock assertions in CI.Measurement
Release builds (
uv run maturin develop --release), seeded corpora (seed 42) from the committed harnesstests/bench_merge_bench.py(opt-inTABLASSERT_BENCH=1), same machine before/after:errormodemerged/scalar_conflictscounters identical on every scenario; the after wall-clocks are conservative (post-Rcre-timing measured C at 1.114–1.185 s).Design
Rcsharing halves the naive two-copy shape (RSS −35% measured).dedup_ndjson's signature and counters are unchanged;rust/src/uuid.rs(golden UUID vectors) andrust/src/fullmap.rsare untouched.Docs
CHANGELOG.md:### Performanceentry under Unreleased with the measured numbers, the reproduction command, and the memory note. No other docs change — behavior is identical, sodocs/configuration/graph.mdandmodels.pyremain accurate.Testing
make check→ EXIT=0uv run pytest→ 1165 passed, 44 skipped, 0 failedcargo test --manifest-path rust/Cargo.toml→ 131 passed, 0 failed (1 ignored:regenerate_golden)cargo test --manifest-path rust/Cargo.toml fold_speedup -- --nocapture→ new 114.9 ms vs reference 3.43 s → 29.9x (bound 5x)uv run ruff check ./uv run ruff format --check ./uv run pyright/cargo fmt --check/cargo clippy --all-targets -- -D warnings→ all cleanTABLASSERT_BENCH=1 uv run --no-sync pytest tests/bench_merge_bench.py -s -n 0 -q→ 6 passed (A/B/C/DUP/E/N)