Skip to content

Micro-optimizations: cache active block parser, ASCII fast path in isLetter#1120

Open
GromNaN wants to merge 2 commits into
thephpleague:2.8from
GromNaN:perf/micro-optimizations
Open

Micro-optimizations: cache active block parser, ASCII fast path in isLetter#1120
GromNaN wants to merge 2 commits into
thephpleague:2.8from
GromNaN:perf/micro-optimizations

Conversation

@GromNaN

@GromNaN GromNaN commented May 13, 2026

Copy link
Copy Markdown
  1. getActiveBlockParser() called end($this->activeBlockParsers) on every call. end() costs ~67 ns; a direct property read costs ~28 ns. The method is called ~5× per line so the overhead adds up. The active parser is now cached in $this->activeBlockParser, kept in sync in activateBlockParser() and deactivateBlockParser().

  2. RegexHelper::isLetter() called preg_match('/[pL]/u', $char) on every non-blank non-indented line to detect whether to skip block-start parsing. For single-byte ASCII characters — the vast majority in Markdown — ctype_alpha() costs ~12 ns vs ~45 ns for the unicode regex.

Micro-benchmarks (PHP 8.5.2, OPcache on, XDEBUG_MODE=off):

Before After Delta
end($parsers) per call 67 ns 28 ns −58%
preg_match Unicode per call 45 ns 12 ns −73%

End-to-end on sample.md (27 KB) with 200 iterations, converter initialized once:

median p95
Before 4.38 ms 6.26 ms
After 3.81 ms 4.91 ms

…n isLetter

Cache the active block parser to avoid calling end() on the parsers array
on every getActiveBlockParser() call. end() costs ~67ns; a direct property
read costs ~28ns. getActiveBlockParser() is called ~5x per line so this
compounds quickly. The cache is kept in sync in activateBlockParser() and
deactivateBlockParser().

Add an ASCII fast path to RegexHelper::isLetter(). The previous
implementation called preg_match('/[\pL]/u', $char) on every non-blank,
non-indented line to detect whether to skip block-start parsing. For
single-byte ASCII characters (the vast majority in Markdown), a direct
range comparison is ~60% faster than the regex.

Micro-benchmarks (PHP 8.5.2, OPcache on, Xdebug off):
  end($parsers)              67 ns  →  property read    28 ns  (-58%)
  preg_match Unicode         45 ns  →  char range       18 ns  (-60%)
@GromNaN GromNaN changed the title Two micro-optimizations: cache active block parser, ASCII fast path in isLetter Micro-optimizations: cache active block parser, ASCII fast path in isLetter May 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant