Skip to content

Commit 352d65c

Browse files
committed
Optimize hot paths for ~10% performance improvement
- Use \G regex anchor in parseSymbol/parseFootnoteRef to avoid redundant strpos() validation (match anchored at offset) - Replace uksort() with array manipulation for attribute ordering in HtmlRenderer (faster id-first sorting) - Add first-char switch in startsNewBlockSignificant() to avoid running 6 regex patterns on every line check Benchmark (medium 56KB document): - Before: 20.68 ms - After: 18.44 ms (~11% faster)
1 parent 3aaed6d commit 352d65c

File tree

3 files changed

+57
-63
lines changed

3 files changed

+57
-63
lines changed

src/Parser/BlockParser.php

Lines changed: 41 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2470,8 +2470,14 @@ protected function appendToLastParagraph(Node $parent, string $content, int $lin
24702470

24712471
protected function startsNewBlock(string $line): bool
24722472
{
2473+
// Quick check: empty lines don't start blocks
2474+
if ($line === '' || !isset($line[0])) {
2475+
return false;
2476+
}
2477+
24732478
// Caption `^ text` can always interrupt paragraphs (special case for figure captions)
2474-
if (preg_match('/^\^ /', $line)) {
2479+
// Quick first-char check before regex
2480+
if ($line[0] === '^' && isset($line[1]) && $line[1] === ' ') {
24752481
return true;
24762482
}
24772483

@@ -2497,37 +2503,43 @@ protected function startsNewBlock(string $line): bool
24972503
*/
24982504
protected function startsNewBlockSignificant(string $line): bool
24992505
{
2500-
// Headings, unordered lists, tables (same as standard)
2501-
if (preg_match('/^(#{1,6}\s|[-*+]\s|\|)/', $line)) {
2502-
return true;
2503-
}
2504-
2505-
// Block quotes
2506-
if (preg_match('/^>\s?/', $line)) {
2507-
return true;
2508-
}
2509-
2510-
// Ordered lists (digit or letter followed by . or ))
2511-
if (preg_match('/^(\d+|[a-zA-Z])[.)]\s/', $line)) {
2512-
return true;
2513-
}
2514-
2515-
// Code fences (with or without info string)
2516-
if (preg_match('/^`{3,}/', $line)) {
2517-
return true;
2518-
}
2506+
// Use first-char switch to avoid unnecessary regex checks
2507+
$first = $line[0];
2508+
2509+
switch ($first) {
2510+
case '#':
2511+
// Headings: #{1,6}\s
2512+
return preg_match('/^#{1,6}\s/', $line) === 1;
2513+
case '-':
2514+
case '*':
2515+
case '+':
2516+
// Unordered lists or thematic breaks
2517+
if (isset($line[1]) && $line[1] === ' ') {
2518+
return true; // Unordered list
2519+
}
25192520

2520-
// Fenced divs
2521-
if (preg_match('/^:{3,}/', $line)) {
2522-
return true;
2523-
}
2521+
// Thematic breaks: *\s*\*\s*\* or -\s*-\s*-
2522+
return preg_match('/^(\*\s*\*\s*\*|-\s*-\s*-)/', $line) === 1;
2523+
case '|':
2524+
// Tables
2525+
return true;
2526+
case '>':
2527+
// Block quotes
2528+
return true;
2529+
case '`':
2530+
// Code fences: `{3,}
2531+
return isset($line[1], $line[2]) && $line[1] === '`' && $line[2] === '`';
2532+
case ':':
2533+
// Fenced divs: :{3,}
2534+
return isset($line[1], $line[2]) && $line[1] === ':' && $line[2] === ':';
2535+
default:
2536+
// Ordered lists: digit or letter followed by . or )
2537+
if (ctype_digit($first) || ctype_alpha($first)) {
2538+
return preg_match('/^(\d+|[a-zA-Z])[.)]\s/', $line) === 1;
2539+
}
25242540

2525-
// Thematic breaks
2526-
if (preg_match('/^(\*\s*\*\s*\*|\-\s*\-\s*\-)/', $line)) {
2527-
return true;
2541+
return false;
25282542
}
2529-
2530-
return false;
25312543
}
25322544

25332545
/**

src/Parser/InlineParser.php

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1564,12 +1564,8 @@ protected function applyAttributesToNode(Node $node, string $attrStr): void
15641564
*/
15651565
protected function parseFootnoteRef(string $text, int $pos): ?array
15661566
{
1567-
// Match [^label]
1568-
if (!preg_match('/\[\^([^\]]+)\]/', $text, $matches, 0, $pos)) {
1569-
return null;
1570-
}
1571-
1572-
if (strpos($text, $matches[0], $pos) !== $pos) {
1567+
// Match [^label] - \G anchors at offset position, avoiding extra strpos check
1568+
if (!preg_match('/\G\[\^([^\]]+)\]/', $text, $matches, 0, $pos)) {
15731569
return null;
15741570
}
15751571

@@ -1645,12 +1641,8 @@ protected function parseMath(string $text, int $pos): ?array
16451641
*/
16461642
protected function parseSymbol(string $text, int $pos): ?array
16471643
{
1648-
// Match :word:
1649-
if (!preg_match('/:([a-zA-Z_][a-zA-Z0-9_-]*):/', $text, $matches, 0, $pos)) {
1650-
return null;
1651-
}
1652-
1653-
if (strpos($text, $matches[0], $pos) !== $pos) {
1644+
// Match :word: - \G anchors at offset position, avoiding extra strpos check
1645+
if (!preg_match('/\G:([a-zA-Z_][a-zA-Z0-9_-]*):/', $text, $matches, 0, $pos)) {
16541646
return null;
16551647
}
16561648

src/Renderer/HtmlRenderer.php

Lines changed: 12 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -399,17 +399,12 @@ protected function renderAttributesExcluding(Node $node, array $exclude): string
399399
$attrs = $this->safeMode->filterAttributes($attrs);
400400
}
401401

402-
// Sort attributes: id first, then others in source order
403-
uksort($attrs, function (string $a, string $b): int {
404-
if ($a === 'id') {
405-
return -1;
406-
}
407-
if ($b === 'id') {
408-
return 1;
409-
}
410-
411-
return 0;
412-
});
402+
// Put id first if present (faster than uksort)
403+
if (isset($attrs['id'])) {
404+
$id = $attrs['id'];
405+
unset($attrs['id']);
406+
$attrs = ['id' => $id] + $attrs;
407+
}
413408

414409
$html = '';
415410
foreach ($attrs as $key => $value) {
@@ -835,17 +830,12 @@ protected function renderAttributes(Node $node): string
835830
$attrs = $this->safeMode->filterAttributes($attrs);
836831
}
837832

838-
// Sort attributes: id first, then others in source order
839-
uksort($attrs, function (string $a, string $b): int {
840-
if ($a === 'id') {
841-
return -1;
842-
}
843-
if ($b === 'id') {
844-
return 1;
845-
}
846-
847-
return 0; // preserve order for other attributes
848-
});
833+
// Put id first if present (faster than uksort)
834+
if (isset($attrs['id'])) {
835+
$id = $attrs['id'];
836+
unset($attrs['id']);
837+
$attrs = ['id' => $id] + $attrs;
838+
}
849839

850840
$html = '';
851841
foreach ($attrs as $key => $value) {

0 commit comments

Comments
 (0)