Skip to content

perf(dedup): remove O(nodes x components) scan from remap construction - #2328

Closed
stupidprogrammer4 wants to merge 1 commit into
Graphify-Labs:v8from
stupidprogrammer4:v8
Closed

perf(dedup): remove O(nodes x components) scan from remap construction#2328
stupidprogrammer4 wants to merge 1 commit into
Graphify-Labs:v8from
stupidprogrammer4:v8

Conversation

@stupidprogrammer4

Copy link
Copy Markdown
Contributor

deduplicate_entities() rebuilt its remap table by re-scanning the whole unique_nodes list once per merged union-find component, costing O(nodes x components) -- roughly 137M membership checks on a 50k-node corpus with 2.5k merged components, about a quarter of dedup wall-clock.

Build an id -> (position, node) index once and slice each component out of it instead: O(members log members) per component.

The index carries the enumeration position, not just the node, because _pick_winner() selects via min(), which returns the first minimum -- so ties (equal chunk-suffix status and equal id length) are resolved by list order. Sorting the group by id instead changed 179 of 2358 survivors on a tie-heavy corpus. Sorting by position reproduces unique_nodes order exactly, leaving survivors and edges byte-identical.

Benchmark, median of 5 runs:

nodes    before     after   speedup
 5000     2.69s     2.51s     1.07x
20000    10.43s     9.00s     1.16x
50000    34.19s    25.07s     1.36x

Isolated remap loop at 50k nodes / 2.5k components: 8.671s -> 0.030s (289x). Merge counts and output node counts are identical at every scale.

This does not address the pre-existing arrival-order dependence of the component structure itself, which originates in the fuzzy-merge loop's uf.find() short-circuit and is out of scope here.

@graphify-labs graphify-labs Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).

Graphify reviewed this change.

Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).


Graphify review — findings

This pull request reverts the 0.9.31 changelog entry and appears to roll back several features/fixes that shipped with it. In cli.py, it removes the per-edge _src/_tgt direction-recovery logic (#2309) from path, query, and explain, restoring behavior that derives direction from stored source/target order, and it removes the ambiguity check that made explain refuse and list candidates when a name matches nodes in multiple files (#2233). It also drops the Go predeclared-function filter import (_GO_PREDECLARED_FUNCS) from extract.py. Separately, dedup.py adds a nodes_by_id index to build the dedup remap in a single pass instead of re-scanning the node list per component, with ordering preserved to keep tie-breaking stable. The diff is truncated, so additional changes across the touched extractor/serve symbols aren't fully visible here.

Worth a look

  • explain no longer refuses ambiguous names across multiple filesgraphify/cli.py:1316 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 2367 functions depend on the 1036 functions this change touches.

Health — this change adds coupling hotspots:

  • worse: _find_node() — 13 callers, 3 callees

Verification — 2367 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 2345 function(s) in the blast radius were not formally verified this run

· 1 more finding(s) on lines outside this diff (see the check run).

deduplicate_entities() rebuilt its remap table by re-scanning the whole
unique_nodes list once per merged union-find component, costing
O(nodes x components) -- roughly 137M membership checks on a 50k-node
corpus with 2.5k merged components, about a third of dedup wall-clock.

Build an id -> (position, node) index once and slice each component out of
it instead: O(members log members) per component.

The index carries the enumeration position, not just the node, because
_pick_winner() selects via min(), which returns the first minimum -- so
ties (equal chunk-suffix status and equal id length) are resolved by list
order. Sorting the group by id instead changed 179 of 2358 survivors on a
tie-heavy corpus. Sorting by position reproduces unique_nodes order
exactly, leaving survivors and edges byte-identical.

Benchmark, median of 5 runs:

    nodes    before     after   speedup
     5000     2.37s     2.29s     1.03x
    20000    10.58s     9.70s     1.09x
    50000    31.72s    21.92s     1.45x

Isolated remap loop at 50k nodes / 2.5k components: 6.838s -> 0.019s (370x).
Merge counts and output node counts are identical at every scale.

This does not address the pre-existing arrival-order dependence of the
component structure itself, which originates in the fuzzy-merge loop's
uf.find() short-circuit and is out of scope here.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@graphify-labs graphify-labs Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).

Graphify reviewed this change.

Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).


Graphify review — findings

This PR refactors the remap-construction loop in deduplicate_entities (in graphify/dedup.py). Instead of re-scanning the full unique_nodes list for each connected component, it builds a single nodes_by_id lookup mapping each id to its position and node, then constructs each group_nodes by looking up members and sorting them by original position. The stated intent is a performance optimization for large corpora, while preserving unique_nodes ordering so that _pick_winner's tie-breaking behavior remains unchanged. The many graphify_dedup_* symbols listed appear to be the surrounding module contents; the actual code change is localized to this loop.

No blocking issues surfaced. 3 lower-confidence candidates did not survive cross-model review.

Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 258 functions depend on the 43 functions this change touches.

Health — grade A; 7 existing hotspot(s) in the area this change touches (pre-existing, not introduced here):

  • deduplicate_entities() — 49 callers, 21 callees (high)
  • dispatch_command() — 2 callers, 110 callees (high)
  • main() — 81 callers, 2 callees (high)
  • build_merge() — 23 callers, 7 callees (high)
  • build() — 19 callers, 3 callees (high)
  • _run() — 12 callers, 1 callees (medium)
  • _llm_tiebreak() — 1 callers, 11 callees (medium)

Verification — 258 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 116 function(s) in the blast radius were not formally verified this run

safishamsi added a commit that referenced this pull request Aug 1, 2026
/#2316/#2339/#2273/#2346/#2332/#2347/#2328

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@safishamsi

Copy link
Copy Markdown
Collaborator

Shipped in v0.9.32: the O(nodes x components) scan is gone from remap construction, with identical results (#2328). Credited in the release notes. Thanks @stupidprogrammer4!

@safishamsi safishamsi closed this Aug 1, 2026
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.

2 participants