perf(cache): memoize source-path (de)normalization per distinct string - #3536
abhay-codes07 wants to merge 1 commit into
Conversation
Both directions of the cache source_file round-trip did per-ITEM path work, though every node in a file's payload — and most of its edges/raw_calls — carries the same one or two source strings, and each mapping is a pure function of (source, root): - _relativize_source_files_in (save): an os.path.abspath, an on-disk exists() stat, and an os.path.relpath per item -> ~2000ms to ~64ms on an 800-item payload sharing one source_file (~31x), almost all of it eliminated stats. - _absolutize_source_files_in (warm load / `graphify update`): a Path build + join + str per item -> ~1150ms to ~65ms (~18x). Each now computes once per distinct string per call. Output is byte-identical (verified against the pre-memo implementation across relative / absolute / out-of-root / missing / duplicate paths, and via a relativize->absolutize round-trip). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JJbLfztSxm2tH5cJwBbe9q
There was a problem hiding this comment.
Graphify reviewed this change.
Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).
Formal verification. No changes could be formally verified in this run.
Graphify review — findings
Memoizes source-path re-anchoring per distinct string in both _relativize_source_files_in and _absolutize_source_files_in, so a file's payload does its abspath/exists()/relpath (or its join) once per unique path instead of once per node/edge/raw_call. Output stays byte-identical since the mapping depends only on the source string and the fixed root, with None meaning "leave unchanged". Adds tests asserting equivalence to the pre-memo implementation, that 500 same-source nodes trigger at most one exists() probe, and that relativize→absolutize round-trips.
No blocking issues surfaced.
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 1576 functions depend on the 99 functions this change touches.
Health — this change adds coupling hotspots:
- new:
extract()— 589 callers, 44 callees - new:
_rebuild_code()— 116 callers, 51 callees - new:
detect()— 112 callers, 15 callees - new:
save_semantic_cache()— 58 callers, 9 callees - new:
load_cached()— 48 callers, 7 callees - new:
file_hash()— 51 callers, 6 callees - new:
extract_corpus_parallel()— 26 callers, 11 callees - new:
dispatch_command()— 2 callers, 124 callees - …and 19 more — each is listed as a finding
Verification — 1576 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: 864 function(s) in the blast radius were not formally verified this run
Test selection
Test selection
23 of 276 test file(s) selected (8%) via static blast radius.
tests/test_cache.py— impacttests/test_charmap_encoding.py— impacttests/test_chunking.py— impacttests/test_definition_file_portability.py— impacttests/test_detect.py— impacttests/test_extract.py— impacttests/test_extract_cache_location.py— impacttests/test_extract_cli.py— impacttests/test_ignore_file_encoding.py— impacttests/test_incremental_mtime_collision.py— impacttests/test_llm_backends.py— impacttests/test_objc_field_table_remap.py— impacttests/test_out_dir_evidence.py— impacttests/test_partial_cache.py— impacttests/test_pipeline.py— impacttests/test_relativize_source_memo.py— impact, changed-testtests/test_semantic_cache_out_root.py— impacttests/test_stale_prune.py— impacttests/test_stat_index_husk.py— impacttests/test_stat_index_portability.py— impacttests/test_watch_manifest_location.py— impacttests/test_word_count_cache.py— impacttests/test_zero_node_no_cache.py— impact
Selection is safe under the controlled-regression assumption; always-run tests + a periodic full run are the backstops. Advisory — it never changes the check verdict.
Formal verification
Could not verify: Could not verify \_absolutize\_source\_files\_in.
The verifier did not have enough to check \_absolutize\_source\_files\_in, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: parameter `root` is annotated `Path` — outside the synthesizable primitive/collection set
Could not verify: Could not verify \_relativize\_source\_files\_in.
The verifier did not have enough to check \_relativize\_source\_files\_in, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: parameter `root` is annotated `Path` — outside the synthesizable primitive/collection set
· 27 more finding(s) on lines outside this diff (see the check run).
What
Both directions of the cache
source_fileround-trip did per-item path work, even though every node in a file's payload — and most of its edges/raw_calls — carries the same one or two source-path strings, and the mapping is a pure function of(source, root):_relativize_source_files_in(the save path) ran anos.path.abspath, an on-diskexists()stat, and anos.path.relpathfor every node/edge/raw_call._absolutize_source_files_in(the warm-load /graphify updatepath) ran aPath(...)build + join +str()for every item.On a file with hundreds of nodes that meant hundreds of redundant path operations — and hundreds of filesystem stats — for one or two distinct paths.
The change
Memoize the mapping per distinct source string within each call (a local dict keyed by the raw string). The value depends only on the string and the fixed
root, so it's computed once per distinct path instead of once per item. No shared/global state; nothing persists across calls.Measured
Isolated microbenchmark, a payload of 800 items (400 nodes + 399 edges) all sharing one
source_file, run 200× (best of 5):_relativize_source_files_in(save)_absolutize_source_files_in(warm load)Almost all of the relativize win is the eliminated per-item
exists()stats. This is squarely on the hot path —save_cachedruns_relativize_source_files_inonce per file on every cold extract, and_absolutize_source_files_inonce per cached file on every warmgraphify update.Correctness
Output is byte-identical to the pre-memo implementation. Verified two ways: an equivalence oracle (the verbatim old code) over a payload mixing relative, absolute-in-root, out-of-root, missing, and duplicate paths; and a
relativize → absolutizeround-trip that recovers the original absolutesource_file. The existing cache/portability suites (318 tests) pass unchanged.Tests
tests/test_relativize_source_memo.py— output matches the unmemoized reference; a 500-node same-source payload triggers at most oneexists()probe (not 500 — fails without the memo); duplicate source strings all rewrite; the absolutize round-trip; and legacy absolute entries pass through untouched.🤖 Generated with Claude Code
https://claude.ai/code/session_01JJbLfztSxm2tH5cJwBbe9q