Repository navigation
Conversation
…eeper pairs Pass 2 of mergeExactSameType compared every keeper pair and rebuilt both slug sets (NFKC + Unicode regex) for each comparison, so normalize() was O(n^2) with regex work per pair. A sync over ~28k nodes ran for 47 minutes without finishing. - Compute each keeper's slug set once; the current keeper's set is extended as it absorbs aliases. - Find candidates through a slug index (exact merge and cross-type clash) and plural/singular/acronym lookups (alias suggestions) and visit them in ascending keeper order, so merges, keepers and review items come out in the same order as the pairwise scan. - Resolve chained id rewrites once at the end with find + path compression instead of rescanning idRewrite on every merge. - Keep hub keepers cheap: cache alias sets across merges, iterate the smaller slug set in the match predicates. Output is unchanged; a new test runs a verbatim copy of the old pass and the new one on seeded synthetic corpora and asserts deep equality, and bounds slugifyLabel calls and visited pairs by the input/output size. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Author
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.
Problem
Pass 2 of
mergeExactSameType(src/pipeline/normalize.ts) compares every pair of keepers. Each comparison rebuilds both nodes' slug sets withslugifyLabel(NFKC plus two Unicode regex replaces). Each merge also rescansidRewrite. Sonormalize()does O(n²) regex work, and a sync stops being practical well before 50k nodes.Fix
The semantics stay those of the pairwise scan. For each keeper
ain order, it is compared with every later keeperbthat has not been dropped.aincludes the aliases it absorbed earlier in the same scan. The change is how candidates are found:slug → keeper indicescovers exact same-type merges and the cross-typecross_type_label_clashreview.alias_suggestionpass uses plural/singular lookups in the same index plus atype + acronym → keeper indicesindex.idRewriteon every merge.suggestsAliasMatchchecks plural/singular/acronym forms by set lookup instead of comparing every slug pair. It is the same symmetric relation with the same length ≥ 2 rule.mergeExactSameTypeis now exported frompipeline/normalizefor tests (not from the package index). It also returns acandidatePairswork counter.Output equivalence
tests/normalize-merge-scale.test.tscontains a verbatim copy of the old pairwise pass. It runs both versions on seeded synthetic corpora and asserts deep equality of:idRewriteentries, in insertion orderCorpora:
The data has colliding labels within and across types, reused and explicit ids, acronym and plural aliases, and empty and punctuation-only labels. The test asserts that each corpus actually produces merges and all three review reasons.
Checks done outside the committed test:
normalize()output (nodes, triples, review items, progress) frommain's build and this branch is identical on 60 seeded corpora (200 to 2,400 nodes), and at 5k, 10k and 20k nodes.A second test bounds the work at 4k and 16k nodes:
slugifyLabelcalls ≤ 4·(nodes + aliases)There is no wall-clock timing in the tests.
Numbers
Full
normalize()on seeded synthetic nodes (with a triple per node), Node 24, one core. Two generator settings:mainWith this branch only, the first setting takes 0.42 s at 40k nodes, 0.95 s at 80k and 2.0 s at 160k.
Not changed
Progress output in non-TTY /
--compactmode is unchanged. Plain progress lines are already available withGSD_GRAPH_PROGRESS=1. Showing them by default would change CLI output for piped use, so it seemed out of scope here. The pass is now short enough that this matters much less.npm run buildandnpm test(435/435) pass on Node 22 and 24;npm run test:coveragepasses on Node 24.Fixes #4
🤖 Generated with Claude Code