Repository navigation
Do not clear the capture cells at every token - #220
Merged
Merged
Conversation
hhugo
marked this pull request as ready for review
October 4, 2026 21:23
`__private__init_mem` filled the memory cells with -1 on entering each `match%sedlex` that has `as` bindings: 23 cells per token for a port of the OCaml lexer, and a handful per character for its string and comment rules. An action only reads cells written on the path of its match, so the fill is not needed; the function now only grows the array. On the OCaml lexer benchmark this was most of the cost of captures over slicing the lexeme by hand. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The function no longer initializes the cells, it only makes sure there are enough of them. Also say why the generated code tests every discriminator on the way to an alternative, which stale cells now make necessary, and that nothing relies on the value fresh cells are created with. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Lex a long input, read through a generator so that the buffer is refilled, with two rules covering nested or-patterns, a loop before an overlapping capture, static offsets and a capture ended by the end of input. Before every token the cells are overwritten: with -1 as when they were cleared, with plausible positions, and with encoded discriminator values. The submatches must be the same as when the cells are left alone. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
hhugo
force-pushed
the
perf-capture-cells
branch
from
October 4, 2026 22:00
51369ee to
bf7d8bf
Compare
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.
Sedlexing.__private__init_memwas called on entering everymatch%sedlexthat hasasbindings, and filled all the memory cells of the rule with -1. On a port of the OCaml compiler's lexer that is 23 cells per token for the main rule, and 4 and 10 cells per character for the string and comment rules, which consume one character per call. The fill cost more than the tag operations themselves.The function now only grows the array, and is renamed
__private__ensure_memto say so. The generated code is otherwise unchanged.Why the fill is not needed
An action only reads cells that were written on the path of its own match:
refillmay touch stale cells, but nothing reads the result.How this was checked
stale_cells_between_tokens, drives one lexbuf through two rules with different numbers of cells and or-patterns whose branches alternate between consecutive tokens.poisoned_cells, lexes about 1100 tokens read through a generator, so that the buffer is refilled, with rules covering nested or-patterns, a loop before an overlapping capture, static offsets and a capture ended by the end of input. Before every token it overwrites the cells with -1, with plausible positions, or with encoded discriminator values; the submatches are the same in every case. Skipping a tag write in the runtime makes it fail.The poisoned runs were one-off checks and are not part of this PR.
Trade-off
Until now, a compiler bug reading a cell that was never written gave a negative position, and usually an exception. It would now return a stale position silently.
Numbers
The OCaml compiler lexer (about 50
asbindings) ported to sedlex, on 8 MB of compiler sources, Latin1 lexbuf, OCaml 5.4.0, release profile, median ns per byte over 11 rounds. "manual" is the same lexer withoutas, slicing the lexeme by hand; "kinds only" extracts no text. Four alternating runs ofmaster(with #219) and this PR.So the lexer with captures is about 19% faster (5 ns per byte), and captures cost 0 to 9% over slicing by hand instead of 27 to 35%. For reference, the same lexer generated by
ocamllex -ml, with its captures, takes 23.3 to 23.8 ns per byte.This lexer is a favourable case: its string and comment rules have captures and are entered once per character, so the fill was paid per character there. A lexer whose rules with captures match whole tokens saves one fill of a few cells per token.
🤖 Generated with Claude Code