Repository navigation
perf(parse): an operator is looked up by its first character, and a dotted name is checked by its dots - #2061
Merged
nicolas-abril merged 5 commits intoNov 5, 2026
Conversation
…otless name skips the name regex parse_term_ops looked for an operator after every term by slicing the next three characters and shortening the slice until INFIX knew it: at most positions no operator starts, so that was three slices and three missed lookups each time, the parser's hottest lines (about 29 ms of generics_3200). A term now ends at once when the next character heads no INFIX key (INFIX_HEADS, derived from the table). parse_lexeme tested every name against the words-joined-by-dots regex; the loop before it only takes letters, digits, _ and ., after a head, so a name without a dot always matched. The regex now runs only on a name with a dot. Checker benches (local, min of 9): generics_3200 0.397 -> 0.376 s, defs_12800 0.311 -> 0.293, proofs_3200 0.816 -> 0.770, compute_1600 and trees_400 unchanged. tests/parse and tests/check (400 files) print the same output, error messages included. bend.ts 45425 -> 45490 ttok. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The loop before the check only takes letters, digits, _ and . after a head, so a name breaks the words-joined-by-dots rule exactly when a dot is not followed by a letter or _ (foo., a..b, Vec.2d). The check now says that, under the dot test, in place of the whole-name regex. Equivalent on every name the loop can take up to 4 characters (14.1M); tests/parse and tests/check print the same output. bend.ts 45490 -> 45478 ttok. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ter heads INFIX_BY_HEAD lists the INFIX keys by first character, longest first, and replaces INFIX_HEADS: the parser tries only the keys the next character heads, with startsWith instead of shrinking slices, and a term ends where none matches, end of input included. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
Two hot spots in the parser, found while profiling why the
generics_3200checker cell sits at 1.12–1.21× its pin. Base plays no part: none of the checker benches imports Base, and swappingbase.bendfor the pin's version or an empty file leaves the time unchanged.parse_term_ops). After every term the parser sliced the next three characters and shortened the slice untilINFIXknew it. At most positions no operator starts, so that was three slices and three missed lookups each time: about 29 ms of generics_3200, the parser's hottest lines (the pin's operator search took about 13 ms).INFIX_BY_HEADnow lists theINFIXkeys by first character, longest first: the parser reads the next character, tries only the keys it heads withstartsWith(no slices), and a term ends at once when it heads none, end of input included. A new operator must be added there too.parse_lexeme). Every name was tested against the whole-name regex^[A-Za-z_]\w*(\.[A-Za-z_]\w*)*$. The loop before it only consumes letters, digits,_and.after a head character, so a name breaks the words-joined-by-dots rule exactly when a dot is not followed by a letter or_(foo.,a..b,Vec.2d). The check is nowk.includes(".") && /\.(?![A-Za-z_])/.test(k): names without a dot never reach a regex, and the regex says the rule directly. It agrees with the old regex on every name the loop can produce up to 4 characters (14.1M names).Both changes keep the parser's behaviour: the first picks the longest key that matches, as the old loop did, and returns exactly where it would have shrunk to
""; the second rejects exactly the names the old regex rejected.Checker benches (local, min of 9 runs)
On the cluster, generics_3200 read 1.09× in a verbose perf run under full gate load (it read 1.12–1.21× on main). Together with #2060 it should land about 1.03×.
Checks
tests/parseandtests/check(458 files) print identical output, error messages included.INFIX_BY_HEADholds exactly the 23INFIXkeys, each under its first character, longest first.bend.ts45425 → 45539 ttok (cap 48000)._run.ts, 3 runs): repo 56/56; ping 81/83, failing 2 BendHub publish checks, as on main; test 1831/1838, failing 7 io/gfx tests withPermission denied, as on main: stale/tmp/bend_io_*files owned by another user on cluster nodes 58 and 72–78; perf 117–119/124, and main gives 118/124 on the same cluster at the same time: merkle and nbody's compile and GPU cells (1.23–1.54× on both) and noisy SEQ-CPU cells. The same base gave 124/124 earlier in the day.🤖 Generated with Claude Code