Skip to content

spec_parser.py: 2x faster#51836

Open
haampie wants to merge 17 commits intodevelopfrom
hs/fix/parser-speed-2
Open

spec_parser.py: 2x faster#51836
haampie wants to merge 17 commits intodevelopfrom
hs/fix/parser-speed-2

Conversation

@haampie
Copy link
Copy Markdown
Member

@haampie haampie commented Jan 12, 2026

  • Fewer allocations: replaced Token wrapper objects with direct usage of re.Match objects; removed TokenContext object creation.
  • Reduced function call overhead: inlined accept/expect logic; removed SpecNodeParser, EdgeAttributeParser, and FileParser classes, flattening the recursive descent into a single SpecParser class.
  • Reduced number of parser iterations: removed explicit WS token; whitespace is now consumed implicitly by the regex engine (C-level) via \s* prefix.
  • Reduced branching: apart from the removal of WS, similar tokens (VERSION/GIT, BOOL/PROPAGATED, DEPENDENCY/START_EDGE_PROPERTIES) are consolidated to reduce branching in parser loop.
  • Sub-string parsing: named capture groups (e.g., bv_prefix, kv_value) are used extract data during matching, avoiding double parsing (notably versions). Also avoid further allocations that exist on develop where named capture groups names had a prefix removed for convenience, resulting in another dictionary per token when named capture groups were used.

benchmark: https://gist.github.com/haampie/fec012851209b7da62ca78b62d31ce87

breakdown

  • baseline: 1783 µs
  • spec.py micro-optimizations: 1783 -> 1716 µs (-67)
  • named capture groups + sub-string parsing: 1716 -> 1672 µs (-44)
  • token consolidation (VERSION/BOOL/KVP/DEPENDENCY merged): 1672 -> 1517 µs (-155)
  • skip WS before Token allocation: 1517 -> 1510 µs (insignificant)
  • inline parser classes + direct dispatch (no accept/expect): 1510 -> 1289 µs (-221)
  • replace Token/generator with re.Match in TokenContext: 1289 -> 966 µs (-323)
  • FAST_SPEC_REGEX (\s* prefix) + flatten curr/next: 966 -> 888 µs (-103)
  • _Spec module-level cache, about ~22 µs

@haampie

This comment was marked as outdated.

@haampie haampie force-pushed the hs/fix/parser-speed-2 branch 2 times, most recently from 6bfbe67 to f598fc8 Compare January 13, 2026 12:56
@tgamblin tgamblin self-requested a review January 13, 2026 18:53
@haampie haampie force-pushed the hs/fix/parser-speed-2 branch from 7234377 to c7c17d4 Compare January 18, 2026 20:41
@haampie haampie force-pushed the hs/fix/parser-speed-2 branch 2 times, most recently from 8aa564b to 153c321 Compare January 23, 2026 15:20
@haampie haampie force-pushed the hs/fix/parser-speed-2 branch 2 times, most recently from 75bf4c7 to 6f21610 Compare March 24, 2026 09:40
haampie added 10 commits March 24, 2026 13:07
Baseline on upstream/develop: 1782.62 μs

Signed-off-by: Harmen Stoppels <[email protected]>
…f parser

total time: 1716.36 μs (prev: 1782.62 μs)

Signed-off-by: Harmen Stoppels <[email protected]>
…(no callers yet)

total time: 1704.15 μs (prev: 1716.36 μs)

Signed-off-by: Harmen Stoppels <[email protected]>
Signed-off-by: Harmen Stoppels <[email protected]>
- Add named subgroups to VERSION, BOOL_VARIANT, KEY_VALUE_PAIR,
  PROPAGATED_BOOL_VARIANT, PROPAGATED_KEY_VALUE_PAIR, VERSION_HASH_PAIR,
  GIT_VERSION token patterns
- Use token.subvalues in SpecNodeParser and EdgeAttributeParser instead
  of slicing token.value
- Use VersionList._from_version_list_string() for VERSION tokens
- Fix tokenize.py Token.__eq__ to treat None subvalues as wildcard

total time: 1671.55 μs (prev: 1683.70 μs)

Signed-off-by: Harmen Stoppels <[email protected]>
- Merge VERSION_HASH_PAIR, GIT_VERSION, VERSION into single VERSION token
  (git_version group present iff it is a git version, else version_list)
- Merge PROPAGATED_BOOL_VARIANT into BOOL_VARIANT (bv_prefix length distinguishes)
- Merge PROPAGATED_KEY_VALUE_PAIR into KEY_VALUE_PAIR (kv_sep contains == for propagated)
- Merge START_EDGE_PROPERTIES into DEPENDENCY (edge_bracket group present iff ^[/%%[/%[)
- tokenize.py: only store non-None subvalues in token.subvalues
- Update spec_syntax.py tests to use consolidated token names

total time: 1517.40 μs (prev: 1671.55 μs)

Signed-off-by: Harmen Stoppels <[email protected]>
total time: 1510.42 μs (prev: 1517.40 μs)

Signed-off-by: Harmen Stoppels <[email protected]>
…ect dispatch

total time: 1289.36 μs (prev: 1510.42 μs)

Signed-off-by: Harmen Stoppels <[email protected]>
haampie added 6 commits March 24, 2026 20:34
…, no generator

Replace the generator-based token stream in TokenContext with a direct
scanner driven by SPEC_TOKENIZER.regex.scanner(). Advance skips WS
and UNEXPECTED checks inline; current_token/next_token now hold re.Match
objects. All dispatch sites updated from Token.kind/subvalues to
Match.lastgroup/group(). _parse_edge_attrs returns (attributes, substitute)
tuple to avoid push_front of synthetic Token.

total time: 966.24 μs (prev: 1289.36 μs)

Signed-off-by: Harmen Stoppels <[email protected]>
Build FAST_SPEC_REGEX with a leading \s* so whitespace is consumed
at the C level before every token match. Drop TokenContext entirely;
SpecParser now holds curr/next re.Match slots and advances inline.
tokens() returns (kind, value, subgroups) tuples; SpecTokens becomes
a plain class of regex strings; SpecTokenizationError re-scans via
FAST_SPEC_REGEX. Cache the Spec class in a module-level _Spec variable
to avoid repeated import lookups in _parse_node.

total time: 875.54 μs (prev: 966.24 μs)

Signed-off-by: Harmen Stoppels <[email protected]>
Pure reorder: move SpecTokenizationError, parse(), parse_one_or_raise(),
toolchain helpers, SpecParsingError, strip_quotes_and_unescape(), and
quote_if_needed() before SpecTokens/FAST_SPEC_REGEX. Also fix conf.py
to import GIT_VERSION_PATTERN, NAME, VERSION, VERSION_LIST from
spec_parser (needed for Sphinx docs). Restore tokenize.py to reference
state. No functional changes.

Brings tree to exact parity with ea96bb6885884b19d18e4517b9bd76d7eef75d13.

total time: 863.45 μs (prev: 875.54 μs)

Signed-off-by: Harmen Stoppels <[email protected]>
Signed-off-by: Harmen Stoppels <[email protected]>
@haampie haampie force-pushed the hs/fix/parser-speed-2 branch from 6f21610 to 75941ca Compare March 24, 2026 19:50
@haampie haampie changed the title spec_parser.py: 1.8x faster spec_parser.py: 2x faster Mar 24, 2026
Replace the custom fast-path with VersionList(version_list_string),
which routes through the existing from_string() path. The -12 µs
saving was not worth the added surface area.

total time: 887.56 µs (prev: 863.45 µs)

Signed-off-by: Harmen Stoppels <[email protected]>
@haampie haampie force-pushed the hs/fix/parser-speed-2 branch from c1dcef7 to ab2b8a4 Compare March 24, 2026 20:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant