Skip to content

perf: ~16% faster parsing by skipping em/strong masking for runs without *, _ or ~ - #4142

Merged
UziTech merged 1 commit into
markedjs:masterfrom
deadstrobe5:perf/skip-emstrong-mask
Oct 26, 2026
Merged

UziTech merged 1 commit into
markedjs:masterfrom
deadstrobe5:perf/skip-emstrong-mask

Conversation

@deadstrobe5

Copy link
Copy Markdown
Contributor

Marked version: master

Markdown flavor: n/a

Description

Performance: marked.parse is ~16% faster on real-world READMEs (and ~9% on the CommonMark spec examples), with byte-identical output.

#inlineTokensInner builds maskedSrc (reflink mask, anyPunctuationOutsideCode replace, blockSkip replace) for every inline run, but only emStrong and del read it, and their left-delimiter regexes all begin with *, _ or ~. 89% of inline runs in real READMEs (23,912 of 26,843 in the corpus below) contain none of these, so that work is thrown away.

This moves the masking into a private #maskSrc() and skips it when the run has no *, _ or ~. It still runs whenever something else could read maskedSrc: an emStrongMask hook is set, or emStrong / del are overridden (via marked.use or a subclass).

No regex was added or changed, so ReDoS exposure is unchanged.

Benchmark

marked.parse, default options, A/B interleaved in one process (alternating order), Node 26, Apple M5:

Corpus Before (median) After (median) Change
300 README.md files from node_modules (2.4 MB), 40 rounds × 5 runs 70.8–81.7 ms 59.4–67.0 ms 15–17% faster
CommonMark spec examples ×20, 30 rounds 42.3 ms 38.2 ms ~9% faster

An A/A run of the original against itself gave a ratio of 1.000–1.010.

Safety

  • HTML output and the full token JSON are byte-identical on all 793 spec files and the README corpus under default, gfm: false, pedantic and breaks options (same SHA-256 before and after).
  • npm test: spec 1911/1911, unit 200/200; test:types and test:lint pass.
  • Added a unit test that a custom emStrong tokenizer still receives the masked source in a run without em delimiters (it fails if the custom-tokenizer check is removed).

Contributor

  • Test(s) exist to ensure functionality and minimize regression (all spec tests and the emStrongMask unit tests cover this; added a unit test for custom emStrong tokenizers); or,
  • no tests required for this PR.
  • If submitting new feature, it has been documented in the appropriate places.

Committer

In most cases, this should be a different person than the contributor.

@vercel

vercel Bot commented Oct 16, 2026

Copy link
Copy Markdown

@deadstrobe5 is attempting to deploy a commit to the MarkedJS Team on Vercel.

A member of the Team first needs to authorize it.

@vercel

vercel Bot commented Oct 17, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
marked-website Ready Ready Preview Oct 17, 2026 3:09am UTC

Request Review

@UziTech UziTech left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for making marked faster! 💯

@UziTech
UziTech merged commit f0fd3e0 into markedjs:master Oct 26, 2026
8 checks passed
github-actions Bot pushed a commit that referenced this pull request Oct 26, 2026
## [18.1.2](v18.1.1...v18.1.2) (2026-10-26)

### Bug Fixes

* allow a tab or end of input after a type 6 html block tag name ([#4128](#4128)) ([e09c40d](e09c40d))
* apply emphasis flanking and rules 9-10 to the whole delimiter run ([#4145](#4145)) ([5d8e234](5d8e234))
* **cli:** await config functions before parsing ([#4138](#4138)) ([3a11ad1](3a11ad1))
* **cli:** create no-clobber output files atomically ([#4135](#4135)) ([e7c4f64](e7c4f64))
* do not continue a block quote paragraph from a whitespace-only line ([#4134](#4134)) ([42f34a8](42f34a8))
* expand a tab after the block quote marker to the next tab stop ([#4110](#4110)) ([2d3899b](2d3899b))
* honor per-call async options when composing hooks ([#4139](#4139)) ([379be9d](379be9d))
* keep a line starting with # but no space in the list item ([#4127](#4127)) ([6b63189](6b63189))
* keep duplicate link definitions in the tokens ([#4132](#4132)) ([2784acc](2784acc))
* keep lazy lines after a list in a nested blockquote ([#4137](#4137)) ([38c7e55](38c7e55))
* keep lazy setext underlines in list items as text ([#4126](#4126)) ([398234c](398234c))
* render nested image alt formatting as plain text ([#4131](#4131)) ([15f59bb](15f59bb))
* stop an unclosed inline raw tag from unescaping later blocks ([#4130](#4130)) ([4562b6f](4562b6f))

### Performance Improvements

* ~16% faster parsing by skipping em/strong masking for runs without *, _ or ~ ([#4142](#4142)) ([f0fd3e0](f0fd3e0))

This branch was successfully deployed

1 active deployment
Preview — 99d06547 Deployed Oct 17, 2026 by vercel[bot]
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.

2 participants