Speed up the local code verbs: prefilter refs, cache map's parse (#83) - #84
Open
tarekziade wants to merge 2 commits into
Open
tarekziade wants to merge 2 commits into
tarekziade wants to merge 2 commits into
Conversation
Parsing is the whole cost of the offline verbs -- on huggingface/transformers reading all 4,883 claimed files is 0.6s and parsing them is 16.4s -- so both changes skip parses and nothing else. refs now parses only the files whose bytes contain the name, the prefilter copies and symbol have had since #45. 10.7s -> 0.6s on a rare name, 10.9s -> 7.1s on a ubiquitous one, byte-identical output throughout. The filter moves to walk.needle so both call sites share it, and it declines -- parses everything -- rather than guess whenever the name has no ASCII trailing identifier run. map has no symbol to filter on, so it memoises instead: a .relore/ directory at the repository root, 18.5s -> 1.0s. Entries are keyed on the sha of the bytes that were parsed and on the provider that parsed them. An earlier draft took the key from git's index and reparsed only what diff-files called dirty, which was 0.6s rather than 1.0s and wrong: git's "clean" is a stat comparison, and under git update-index --assume-unchanged a cached map reported the previous definition while the file on disk held a new one. Reading every file back is what that costs, and it is affordable because reading was never the cost. No reference positions are cached; map ranks on counts and refs recomputes every line it prints. defs is untouched -- the same code runs in relored against historical blobs, where a working-tree cache is actively wrong. The directory ignores itself, and RELORE_NO_CACHE turns the whole thing off. benchmarks/probes/code_lens.py measures both halves, because either speedup could be made faster by returning less: speed against git grep and grep -rn across rare-to-ubiquitous symbols, a strict-subset check accounting for every line grep has that the lens does not, and an assertion that cached and uncached runs are byte-identical. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
One directory per tree, one thing to delete, one RELORE_NO_CACHE. Records the three rules #83 paid for -- content keys, everything that changes the answer in the key, and a probe that asserts the two paths agree -- and the verbs that must stay exempt. The two layers that do not exist yet are #85. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Collaborator
Author
|
Added a commit making the Filed the follow-on work rather than growing this PR:
|
This branch has not been deployed
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.
Closes #83.
Parsing is the whole cost of the offline verbs — on
huggingface/transformers@d9890f6(4,883 claimed files) reading them all is 0.6 s and parsing them is 16.4 s — so both levers skip parses and nothing else.Lever 1 —
refsuses the #45 prefilterA file whose bytes lack the identifier cannot reference it.
survey.pyhas had this since #45;refstakes a single symbol, so the argument carries over unchanged. The filter moved towalk.needleso both call sites share one.refs use_kernels(9/4883 files)refs compute_default_rope_parameters(198)refs forward(1648)refs config(3182)The candidate counts reproduce the issue's table exactly. Two things the ordering protects: the
refs-tier check still runs on every claimed file, sounsupported/completecannot go quietly wrong; and a prefiltered file still counts assearched, because it was searched (#37).needlenow declines to filter — parses everything — when a name has no ASCII trailing identifier run. Falling back to the whole string madeVec<T>its own needle, which is in no file, so every file would be rejected and the verb would answer "nothing found" confidently.Lever 2 — a
.relore/parse cache, formaponlymaphas no query symbol, so visiting everything is the verb. It memoises instead: 18.5 s → 1.0 s, and it is the only writer, because it is the one verb that already computes both halves of an entry.The key is the content, and this cost a redesign
The issue proposed keying on
git ls-files -sand reparsing only whatgit diff-filescalls dirty. That was built and measured at 0.6 s instead of 1.0 s — and it is wrong. git's "clean" is a comparison of stat data, not of content. Reproduced on a throwaway repo:That is precisely the staleness hazard
walk.pyrefused a cache over, wearing git's name. So every file is read and hashed on every call and only the parse is skipped. Reading is affordable because it was never the cost. git is still consulted for one thing that cannot be wrong: where the cache file lives.Also: the provider is half the key (a
pip uninstall tree-sitter-pythonmoves every.pyto ctags — a different parse of identical bytes); no reference positions are stored (mapranks on counts,refsrecomputes every line it prints);defsis untouched, since the same code runs inreloredagainst historical blobs where a working-tree cache is actively wrong; the directory writes a.gitignoreof*so it never appears in yourgit status; andRELORE_NO_CACHEremoves the path entirely.refsdoes not read the cache. It was built, benchmarked and removed — it cost up to 1.3 s and never won. Once rule 1 took away read-skipping, all a cache can save is a parse, which is exactly what the prefilter already does for a single named symbol.The benchmark —
benchmarks/probes/code_lens.pyClient-side, beside
page_cost.pyrather than inbench/(which is §10's server-side retrieval set). Both halves, because either speedup could be made faster by returning less.Zero unexplained on every symbol, and
use_kernelsreproduces the issue's 45-of-66 with its 12 + 9 split exactly. Every grep line the lens omits is accounted for by one of three independently-established reasons: outside the walk (taken fromwalk.source_filesitself, not guessed), a longer identifier containing the substring, or prose — decided bytokenize, the stdlib's own lexer, deliberately a different implementation from the tree-sitter grammar under test. A per-line regex could not see that a docstring's fourth line is inside a docstring and reported 867 of them as losses.Anything unexplained, any location the lens reports that grep cannot see, or any cached/uncached divergence is printed in full and exits non-zero.
Notes for review
walk.py's "Nothing is cached" paragraph is rewritten rather than deleted: "fast enough" was measured and was false; "staleness hazard" was true and survives as the constraint that produced rule 1.RELORE_NO_CACHEas a client-visible surface.🤖 Generated with Claude Code