syntax-highlighter-bench results

Timed on laptop1 (AMD Ryzen 5 PRO 7530U with Radeon Graphics, Node v24.14.1) on 2026-10-11 at commit bd12c165af. The machine's noise floor is 2.3% for one cell and 0.3% for a geometric mean: a smaller difference is not a result. The method says how each number is measured and what it leaves out.

The machine and the run

laptop1: AMD Ryzen 5 PRO 7530U with Radeon Graphics, 12 threads, performance governor, amd-pstate-epp driver, energy preference performance, boost on, 14.4 GB, kernel 6.12.90+deb13.1-amd64. Corpus e649d70d43. The anchor, a fixed workload read before and after measuring, drifted 2.2%: stable. Two processes measuring the same library on the same cell in this run were 4.0% apart at the 95th percentile and 0.9% at the median, against 4.1% at the 95th percentile in the calibration.

Each row is one thing measured, with a column for each library. Beside a number is its ratio to the best in the row, or to a reference library you choose: 1.00× is the baseline and 2.00× is twice as slow or twice as large. In the tables whose ratios aren't colored, where a smaller number isn't a better one, the baseline is the smallest. A library with no number in a row says why: unsupported when its adapter doesn't claim the language, and excluded when it claims it and its output failed the check that runs before measuring. Neither is a zero, and neither enters a mean.

How many times the baseline a tint means:
  • under 0.9×, better than the reference
  • 0.9–1.1×, level
  • 1.1–2×
  • 2–5×
  • 5–10×
  • 10–100×
  • 100× or more

Compare

The selection is kept in the address, so a comparison is a link.

Libraries
Languages
Input sources

A home source is a library's own samples: home turf for that library, and always labeled with its name. Home inputs are summarized by owner and never pooled. The corpus docs list every input.

More filters
Sections
Engine kind
HTML output
Input sizes
Modes

Throughput

A warm library on one input, in one mode. Each language shows a geometric mean per mode; open it for the inputs the mean is over.

How to read this table

MB/s compares across inputs of different sizes, and time per call does not: inputs in one size tier can differ several times over in bytes. A geometric mean covers only the rows where every selected library has a number, and says how many. A disturbed mark is a value whose rounds disagreed, and retried one that was measured again. A ratio marked ≈ is inside the machine's noise floor.

inputfuz_codemaintainer's Twinkleplop Prism Shiki (JS engine) Shiki (wasm)
Every selected language
geomean · neutral · tokenize over 27 of 27 rows 234 MB/s 1.00× 64.3 MB/s 3.65× 14.8 MB/s 15.8× 1.24 MB/s 189× 1.27 MB/s 185×
geomean · neutral · html over 27 of 27 rows 69.0 MB/s 1.00× 34.8 MB/s 1.98× 7.91 MB/s 8.73× 0.86 MB/s 80.5× 0.94 MB/s 73.6×
TypeScript · neutral real code no measured library authored
geomean · tokenize over 3 of 3 rows 165 MB/s 1.00× 27.6 MB/s 5.99× 6.45 MB/s 25.6× 0.33 MB/s 505× 0.74 MB/s 224×
geomean · html over 3 of 3 rows 57.9 MB/s 1.00× 19.3 MB/s 3.00× 4.80 MB/s 12.0× 0.30 MB/s 194× 0.62 MB/s 93.1×
JS · neutral real code no measured library authored
geomean · tokenize over 3 of 3 rows 179 MB/s 1.00× 31.7 MB/s 5.64× 10.7 MB/s 16.7× 0.32 MB/s 560× 0.69 MB/s 261×
geomean · html over 3 of 3 rows 59.2 MB/s 1.00× 21.4 MB/s 2.76× 6.78 MB/s 8.73× 0.29 MB/s 203× 0.58 MB/s 103×
CSS · neutral real code no measured library authored
geomean · tokenize over 3 of 3 rows 219 MB/s 1.00× 63.9 MB/s 3.42× 31.5 MB/s 6.96× 2.42 MB/s 90.3× 0.81 MB/s 269×
geomean · html over 3 of 3 rows 69.9 MB/s 1.00× 34.9 MB/s 2.00× 10.7 MB/s 6.51× 1.28 MB/s 54.7× 0.62 MB/s 113×
HTML · neutral real code no measured library authored
geomean · tokenize over 3 of 3 rows 223 MB/s 1.00× 76.8 MB/s 2.90× 15.9 MB/s 14.0× 0.78 MB/s 284× 1.03 MB/s 216×
geomean · html over 3 of 3 rows 44.0 MB/s 1.00× 32.4 MB/s 1.36× 6.65 MB/s 6.62× 0.60 MB/s 73.0× 0.78 MB/s 56.6×
JSON · neutral real code no measured library authored
geomean · tokenize over 3 of 3 rows 385 MB/s 1.00× 190 MB/s 2.03× 48.7 MB/s 7.90× 5.82 MB/s 66.1× 4.55 MB/s 84.7×
geomean · html over 3 of 3 rows 94.3 MB/s 1.00× 64.2 MB/s 1.47× 15.3 MB/s 6.15× 2.75 MB/s 34.3× 2.40 MB/s 39.4×
Svelte · neutral real code no measured library authored
geomean · tokenize over 3 of 3 rows 118 MB/s 1.00× 24.2 MB/s 4.89× 8.82 MB/s 13.4× 0.24 MB/s 499× 0.39 MB/s 300×
geomean · html over 3 of 3 rows 41.8 MB/s 1.00× 17.1 MB/s 2.45× 5.22 MB/s 8.02× 0.22 MB/s 190× 0.35 MB/s 120×
Markdown · neutral real code no measured library authored
geomean · tokenize over 3 of 3 rows 354 MB/s 1.00× 161 MB/s 2.19× 10.7 MB/s 33.2× 10.5 MB/s 33.6× 5.82 MB/s 60.8×
geomean · html over 3 of 3 rows 125 MB/s 1.00× 83.8 MB/s 1.50× 9.29 MB/s 13.5× 4.85 MB/s 25.9× 3.57 MB/s 35.2×
Bash · neutral real code no measured library authored
geomean · tokenize over 3 of 3 rows 293 MB/s 1.00× 79.0 MB/s 3.71× 8.10 MB/s 36.2× 1.54 MB/s 191× 1.37 MB/s 214×
geomean · html over 3 of 3 rows 92.3 MB/s 1.00× 43.1 MB/s 2.14× 5.67 MB/s 16.3× 1.12 MB/s 82.3× 1.04 MB/s 88.4×
Rust · neutral real code no measured library authored
geomean · tokenize over 3 of 3 rows 313 MB/s 1.00× 74.3 MB/s 4.21× 27.3 MB/s 11.5× 1.55 MB/s 202× 1.38 MB/s 227×
geomean · html over 3 of 3 rows 73.7 MB/s 1.00× 40.6 MB/s 1.81× 12.3 MB/s 5.98× 1.13 MB/s 65.0× 1.03 MB/s 71.5×

Cold start

Loaded as

What each library adds to a fresh Node process: the scenario's median time less bare Node's. A row's details give the whole time and the process's peak memory.

How to read this table

Bundled is the one chunk whose size is reported below, and unbundled imports the same entry with its imports resolved from node_modules. A ratio between small additions is large even when the processes take nearly the same time.

An addition smaller than bare Node's own band, from its 10th to its 90th percentile, is marked in the noise: it can't be told from adding nothing, and has no ratio. In a row with such an addition the others are compared with that band instead of with the best, and marked ≥: at least so many times the band, and so at least so many times the best, which is inside it. Such a row is left out of the mean. A reference library is used only in the rows where its own addition is outside the noise.

loadedfuz_codemaintainer's Twinkleplop Prism Shiki (JS engine) Shiki (wasm)
bundled · import the library with no language
core 0.75 ms in the noise 1.83 ms ≥1.26× 1.41 ms in the noise 10.7 ms ≥7.40× 50.5 ms ≥34.9×
bundled · import the library with one language
geomean over 3 of 9 rows: the others lack an exact number for a library and are left out 1.79 ms 1.00× 15.5 ms 8.67× 2.66 ms 1.48× 17.3 ms 9.68× 57.9 ms 32.3×
bundled · import the library with a feature set
docs 2.69 ms 1.00× 19.9 ms 7.40× 3.76 ms 1.40× 30.6 ms 11.4× 72.2 ms 26.9×
bundled · import one language and highlight once
geomean over 7 of 9 rows: the others lack an exact number for a library and are left out 2.72 ms 1.00× 14.4 ms 5.29× 7.72 ms 2.84× 139 ms 51.2× 128 ms 47.1×

Bundle size

What each language costs in a library's minified JS: the floor with no language, the first language, and one more beside the others.

How to read this table

The first language is the bundle with that language alone, less the bundle with none. One more language is what it adds to a bundle of every other timed language, always in minified bytes, whatever the Bytes selector says: compressing a whole bundle doesn't separate one part of it. The floor plus each language alone is not a bundle, so it has no ratio: it exceeds the set's measured size when a library shares code between languages, or when one language brings another with it, as HTML brings the script and style languages. The wasm binary and the theme stylesheet are loaded beside the JS, and each has a row of its own.

languagefuz_codemaintainer's Twinkleplop Prism Shiki (JS engine) Shiki (wasm)
The floor the library with no language
core 1.31 kB 1.00× 16.4 kB 12.5× 3.56 kB 2.71× 56.4 kB 42.9× 38.8 kB 29.6×
The first language the language alone, less the floor: with any runtime the floor leaves out
TypeScript 4.80 kB 2.26× 27.7 kB 13.0× 2.13 kB 1.00× 15.6 kB 7.35× 15.7 kB 7.36×
JS 4.80 kB 2.68× 24.9 kB 13.9× 1.79 kB 1.00× 16.1 kB 8.98× 16.1 kB 9.00×
CSS 1.19 kB 2.15× 11.0 kB 19.9× 552 B 1.00× 11.8 kB 21.3× 11.8 kB 21.4×
HTML 7.76 kB 2.56× 24.9 kB 8.20× 3.04 kB 1.00× 38.7 kB 12.8× 39.0 kB 12.8×
XML not timed 2.34 kB 2.47× — unsupported: no xml949 B 1.00× 4.94 kB 5.20× 4.97 kB 5.24×
JSON 695 B 3.26× 606 B 2.85× 213 B 1.00× 610 B 2.86× 615 B 2.89×
Svelte 8.44 kB 2.48× 25.9 kB 7.63× 3.40 kB 1.00× 46.6 kB 13.7× 46.5 kB 13.7×
Markdown 3.68 kB 1.59× 2.31 kB 1.00× 2.65 kB 1.15× 5.55 kB 2.40× 5.57 kB 2.41×
Bash 3.36 kB 1.11× 12.8 kB 4.20× 3.04 kB 1.00× 5.90 kB 1.94× 5.93 kB 1.95×
Rust 2.58 kB 2.32× 13.7 kB 12.3× 1.11 kB 1.00× 2.55 kB 2.29× 2.56 kB 2.30×
One more language every timed language, less all of them but this one, in minified bytes: near zero when the others already bring it in, as HTML and Svelte bring their script and style languages
TypeScript 0 B 11.1 kB 1.29 kB 3 B 3 B
JS 0 B 109 B 0 B 3 B 3 B
CSS 0 B 95 B 0 B 3 B 3 B
HTML 88 B 97 B 0 B 57.6 kB 57.6 kB
JSON 1.39 kB 3.09× 1.03 kB 2.29× 449 B 1.00× 2.80 kB 6.23× 2.80 kB 6.23×
Svelte 2.39 kB 1.00× 4.81 kB 2.02× 2.55 kB 1.07× 24.5 kB 10.2× 24.5 kB 10.2×
Markdown 4.38 kB 1.00× 6.73 kB 1.54× 5.24 kB 1.19× 59.3 kB 13.5× 59.3 kB 13.5×
Bash 8.09 kB 1.32× 7.67 kB 1.25× 6.14 kB 1.00× 41.5 kB 6.75× 41.5 kB 6.75×
Rust 6.29 kB 2.53× 10.6 kB 4.28× 2.49 kB 1.00× 15.0 kB 6.05× 15.0 kB 6.05×
Set: docs js, ts, css, html, md, bash, json
floor plus each language alone the floor plus each language alone: not a bundle 27.6 kB 121 kB 17.0 kB 151 kB 134 kB
measured one bundle of them all 13.4 kB 1.16× 49.1 kB 4.26× 11.5 kB 1.00× 122 kB 10.6× 104 kB 9.06×
Loaded beside the JS each on its own
wasm binary nonenonenonenone159 kB
theme stylesheet 643 B 1.00× 2.13 kB 3.30× 657 B 1.02× inline styles: the theme is in the JSinline styles: the theme is in the JS
color schemes the theme covers light + darklight + darklightlightlight

Install footprint

What npm install of each library adds to a project: its packages and their runtime dependencies, each package once, with the files they hold and those files' bytes.

How to read this table

It is everything the packages ship, every language, theme, and type declaration included, and not what a page loads, which the tables above show. The ratios are to the smallest and are not colored: a library that ships more languages installs more, and installs it once.

measurefuz_codemaintainer's Twinkleplop Prism Shiki (JS engine) Shiki (wasm)
Install footprint the packages and their runtime dependencies, each package once
packages 6 3.00× 11 5.50× 2 1.00× 45 22.5× 45 22.5×
files 391 6.86× 57 1.00× 706 12.4× 2,422 42.5× 2,422 42.5×
bytes 2.16 MB 3.21× 672 kB 1.00× 2.06 MB 3.06× 15.0 MB 22.2× 15.0 MB 22.2×

Retained heap

Parts

What a process holds once a library has loaded one language and highlighted a small snippet once, over a bare process: a smaller figure is less memory for the same highlight.

How to read this table

A Node process imports the library's bundle of that language alone, the one whose size is reported above, highlights the language's snippet, lets go of the HTML, and collects garbage until the heap settles. Larger inputs leave more, most of all for regexp and wasm engines.

Machine code holds the regular expressions the engine compiled as well as optimized functions, and is the processor's: these figures are x64's. External memory is where a WebAssembly engine keeps its linear memory, counted at the size it has grown to, which it never gives back; the OS backs only the pages it has touched, so resident memory can be lower. The sum of the three is shown first and ranked, so neither a regexp engine nor a wasm engine is compared without what it holds; the Parts filter shows each on its own. A ratio within the heap check's tolerance is marked ≈.

A theme in a stylesheet is held by the page, not the library, so a library that writes its styles inline holds its theme here and one with classes doesn't. The figures are for one Node version; the deterministic metrics docs say which, and what moves them.

languagefuz_codemaintainer's Twinkleplop Prism Shiki (JS engine) Shiki (wasm)
Everything held the V8 heap, its machine code, and the external memory, summed
TypeScript 210 kB 1.00× 2.53 MB 12.1× 349 kB 1.66× 7.53 MB 35.9× 46.2 MB 220×
JS 199 kB 1.00× 1.90 MB 9.58× 333 kB 1.68× 5.69 MB 28.6× 33.4 MB 168×
CSS 92.2 kB 1.00× 801 kB 8.69× 122 kB 1.32× 2.93 MB 31.8× 20.2 MB 220×
HTML 282 kB 1.00× 1.82 MB 6.48× 457 kB 1.62× 7.48 MB 26.6× 22.5 MB 79.9×
JSON 73.7 kB 1.00× 589 kB 7.99× 100 kB 1.36× 1.67 MB 22.7× 19.9 MB 269×
Svelte 316 kB 1.00× 2.08 MB 6.56× 479 kB 1.51× 9.88 MB 31.2× 56.9 MB 180×
Markdown 127 kB 1.00× 773 kB 6.09× 349 kB 2.75× 3.37 MB 26.6× 20.9 MB 164×
Bash 145 kB 1.00× 1.18 MB 8.13× 728 kB 5.01× 2.60 MB 17.9× 20.2 MB 139×
Rust 132 kB 1.00× 969 kB 7.33× 193 kB 1.46× 2.14 MB 16.2× 20.0 MB 151×

Tokens and output

Numbers

How many tokens each library finds in an input, and how large its HTML is: in elements, and in bytes, raw or compressed as a server would send it. Open a language for its inputs.

How to read this table

The ratios are to the smallest in each row and are not colored, because smaller is not better here: libraries split the same source differently and wrap their HTML differently, so fewer tokens is a coarser split, not a better one, and HTML with inline styles carries its theme where HTML with classes leaves it to a stylesheet. The deterministic metrics docs say what each counts.

inputfuz_codemaintainer's Twinkleplop Prism Shiki (JS engine) Shiki (wasm)
TypeScript · neutral real code no measured library authored
JS · neutral real code no measured library authored
CSS · neutral real code no measured library authored
HTML · neutral real code no measured library authored
JSON · neutral real code no measured library authored
Svelte · neutral real code no measured library authored
Markdown · neutral real code no measured library authored
Bash · neutral real code no measured library authored
Rust · neutral real code no measured library authored