Guides

Benchmarks

Per-plugin overhead can be measured with:

npm run benchmark:plugins -- --time=100 --warmup=25

This compares core rendering with core + one plugin for every official plugin and writes apps/benchmarks/results/plugin-overhead-latest.json.

Nizel keeps two benchmark reports: an engine comparison against other Markdown parsers and an internal function benchmark for finding parser, renderer, and pipeline bottlenecks.

The numbers below are from the local benchmark suite using the current Nizel worktree. Higher Hz and higher scores are better. Lower mean milliseconds are better.

Summary

MetricValue
Benchmark run5 May 2026, 12:31 (2026-05-05T10:31:06.691Z)
Function run5 May 2026, 12:34 (2026-05-05T10:34:02.886Z)
Run labelafter-render-fast-path
Nodev22.12.0
Platformdarwin-arm64
Nizel source../nizel
Nizel distpackages/nizel/dist/
Time / warmup700ms / 300ms
Function time / warmup500ms / 200ms
markdown-it14.1.1
marked18.0.3
remark11.0.0
tinybench4.1.0

Fixtures

  • code-heavy: 700 bytes, 46 lines
  • gfm: 559 bytes, 23 lines
  • large: 2093 bytes, 83 lines
  • readme: 997 bytes, 44 lines
  • small: 191 bytes, 14 lines

Engine Rankings

Score is the geometric mean of relative speed across fixtures for the mode. A score of 100 means that engine was fastest for every fixture in that mode.

ModeRankEngineResultScoreGraphAverage RelativeWinsFixtures
parse1nizel-localbest91.8
92.6%35
parse2markdown-it87.1
87.8%25
parse3marked56.1
58.0%05
parse4remark4.0
4.0%05
render1markdown-itbest93.2
93.6%25
render2nizel-local89.9
90.7%25
render3marked65.7
67.6%15
render4remark4.5
4.6%05

Fixture Winners

  • nizel-local: 5 wins
  • markdown-it: 4 wins
  • marked: 1 wins

Benchmark Tests

Each benchmark test is shown first, followed by the result for every implementation in that exact test.

code-heavy / parse

Test: Markdown string to parser output. Fixture: code-heavy (700 bytes, 46 lines).

RankImplementationResultHzMean msRelativeSlowerSamples
1nizel-localbest139,733.690.0073100.0% of fastest95,299
2markdown-it124,013.450.008388.7% of fastest11.3%84,777
3marked123,466.580.008288.4% of fastest11.6%85,128
4remark5,204.810.19603.7% of fastest96.3%3,572

code-heavy / render

Test: Markdown string to HTML string. Fixture: code-heavy (700 bytes, 46 lines).

RankImplementationResultHzMean msRelativeSlowerSamples
1markedbest92,707.840.0109100.0% of fastest63,993
2markdown-it91,168.950.011298.3% of fastest1.7%62,358
3nizel-local89,634.400.012196.7% of fastest3.3%57,752
4remark4,774.630.21395.2% of fastest94.8%3,273

gfm / parse

Test: Markdown string to parser output. Fixture: gfm (559 bytes, 23 lines).

RankImplementationResultHzMean msRelativeSlowerSamples
1nizel-localbest52,830.940.0197100.0% of fastest35,603
2markdown-it38,062.060.027072.0% of fastest28.0%25,902
3marked31,147.460.032859.0% of fastest41.0%21,358
4remark2,288.020.45674.3% of fastest95.7%1,533

gfm / render

Test: Markdown string to HTML string. Fixture: gfm (559 bytes, 23 lines).

RankImplementationResultHzMean msRelativeSlowerSamples
1nizel-localbest41,937.800.0247100.0% of fastest28,386
2markdown-it32,623.460.031877.8% of fastest22.2%22,018
3marked29,018.390.035169.2% of fastest30.8%19,938
4remark1,979.710.52444.7% of fastest95.3%1,335

large / parse

Test: Markdown string to parser output. Fixture: large (2093 bytes, 83 lines).

RankImplementationResultHzMean msRelativeSlowerSamples
1markdown-itbest17,882.650.0575100.0% of fastest12,172
2nizel-local12,419.420.082569.4% of fastest30.6%8,488
3marked8,010.820.126844.8% of fastest55.2%5,522
4remark747.151.36254.2% of fastest95.8%514

large / render

Test: Markdown string to HTML string. Fixture: large (2093 bytes, 83 lines).

RankImplementationResultHzMean msRelativeSlowerSamples
1markdown-itbest14,881.500.0694100.0% of fastest10,084
2nizel-local10,220.570.101068.7% of fastest31.3%6,931
3marked7,543.960.134950.7% of fastest49.3%5,191
4remark636.171.67004.3% of fastest95.7%420

readme / parse

Test: Markdown string to parser output. Fixture: readme (997 bytes, 44 lines).

RankImplementationResultHzMean msRelativeSlowerSamples
1nizel-localbest57,310.520.0181100.0% of fastest38,639
2markdown-it44,880.980.024078.3% of fastest21.7%29,216
3marked25,211.050.041344.0% of fastest56.0%16,954
4remark1,873.660.54263.3% of fastest96.7%1,291

readme / render

Test: Markdown string to HTML string. Fixture: readme (997 bytes, 44 lines).

RankImplementationResultHzMean msRelativeSlowerSamples
1nizel-localbest41,532.940.0263100.0% of fastest26,648
2markdown-it38,241.540.026992.1% of fastest7.9%26,030
3marked23,486.820.043556.5% of fastest43.5%16,076
4remark1,628.180.62583.9% of fastest96.1%1,120

small / parse

Test: Markdown string to parser output. Fixture: small (191 bytes, 14 lines).

RankImplementationResultHzMean msRelativeSlowerSamples
1markdown-itbest136,790.820.0076100.0% of fastest91,918
2nizel-local128,187.770.008193.7% of fastest6.3%86,076
3marked73,956.430.014754.1% of fastest45.9%47,638
4remark6,062.500.17114.4% of fastest95.6%4,093

small / render

Test: Markdown string to HTML string. Fixture: small (191 bytes, 14 lines).

RankImplementationResultHzMean msRelativeSlowerSamples
1markdown-itbest113,782.040.0093100.0% of fastest75,126
2nizel-local100,556.020.010388.4% of fastest11.6%67,963
3marked70,298.990.014761.8% of fastest38.2%47,754
4remark5,379.130.19474.7% of fastest95.3%3,596

Nizel Internal Costs

The internal benchmark separates public pipeline calls from parser/rendering phases and inline parsing. These rows are useful for deciding where optimization work should start.

CategoryFunctionsTotal mean msGraphAverage mean msSlowest functionFixtureSlowest ms
pipeline300.7704
0.0257useNizel().htmllarge0.0984
phase250.2014
0.0081parseMarkdownlarge0.0877
inline100.1429
0.0143stripInlineMarkdownlarge0.0436

Function Results

RankCategoryFixtureFunctionMean msCostHzSamplesRME
1pipelinelargeuseNizel().html0.0984
10,335.085,0800.54
2pipelinelargeuseNizel().text0.0965
10,548.085,1840.53
3pipelinelargeuseNizel.preset("minimal").html0.0909
11,211.835,5030.56
4phaselargeparseMarkdown0.0877
11,894.605,7040.93
5pipelinelargeuseNizel().ast0.0861
11,975.505,8070.73
6pipelinelargeuseNizel().parse0.0829
12,280.026,0290.53
7inlinelargestripInlineMarkdown0.0436
23,495.0911,4790.60
8inlinelargeparseInline0.0425
24,160.1911,7630.62
9pipelinegfmuseNizel().html0.0248
41,560.3820,2020.56
10pipelinereadmeuseNizel().html0.0240
43,035.8320,8020.65
11pipelinereadmeuseNizel().text0.0236
44,359.7121,1660.80
12pipelinegfmuseNizel().text0.0232
44,366.5421,5680.57
13pipelinegfmuseNizel().ast0.0198
51,897.8925,2860.52
14pipelinegfmuseNizel().parse0.0195
52,602.4525,6410.51
15pipelinegfmuseNizel.preset("minimal").html0.0194
53,092.9325,7420.59
16phasegfmparseMarkdown0.0193
53,079.8425,8620.53
17pipelinereadmeuseNizel().ast0.0191
55,174.6526,1640.85
18phasereadmeparseMarkdown0.0179
57,800.9927,8750.68
19pipelinereadmeuseNizel().parse0.0178
57,920.7328,0960.59
20pipelinereadmeuseNizel.preset("minimal").html0.0168
63,111.4329,7790.91
21inlinegfmstripInlineMarkdown0.0124
82,142.0140,1810.46
22phaselargerenderHtml0.0124
83,082.9340,2550.53
23inlinegfmparseInline0.0122
83,797.9441,1210.40
24phaselargecollect0.0119
86,553.2741,9020.55
25pipelinecode-heavyuseNizel().html0.0116
89,780.0243,2790.40
26inlinecode-heavystripInlineMarkdown0.0114
89,508.0143,7100.47
27inlinecode-heavyparseInline0.0111
91,864.4244,9960.43
28pipelinecode-heavyuseNizel().text0.0104
98,975.1048,1760.45
29pipelinesmalluseNizel().html0.0102
100,565.2148,9920.47
30pipelinecode-heavyuseNizel.preset("minimal").html0.0101
101,675.9549,4550.50
31pipelinesmalluseNizel().text0.0098
104,797.2450,8000.51
32pipelinesmalluseNizel.preset("minimal").html0.0087
117,934.1557,2480.48
33pipelinesmalluseNizel().ast0.0083
123,581.8060,2430.46
34pipelinesmalluseNizel().parse0.0081
126,851.9261,5940.49
35phasecode-heavyparseMarkdown0.0080
135,723.7862,1361.28
36pipelinecode-heavyuseNizel().ast0.0080
130,661.8462,7020.55
37phasesmallparseMarkdown0.0079
128,977.3862,9210.44
38pipelinecode-heavyuseNizel().parse0.0073
139,781.1068,1580.35
39phasereadmerenderHtml0.0049
212,206.10101,4580.72
40phasereadmecollect0.0043
246,244.37116,3051.62
41inlinesmallstripInlineMarkdown0.0039
261,309.00126,5950.51
42phasegfmrenderHtml0.0039
264,336.24127,2120.60
43inlinesmallparseInline0.0037
281,343.97136,0130.59
44pipelinelargeuseNizel().meta0.0032
320,164.90155,6790.46
45pipelinereadmeuseNizel().meta0.0032
337,595.49157,8050.85
46phasecode-heavyrenderHtml0.0031
330,197.20160,4860.44
47pipelinecode-heavyuseNizel().meta0.0031
339,574.45162,1080.57
48phasegfmcollect0.0029
363,833.93173,8340.68
49pipelinegfmuseNizel().meta0.0028
363,632.43176,9850.48
50pipelinesmalluseNizel().meta0.0028
375,014.08181,2480.87
51phasecode-heavycollect0.0025
407,025.84198,9440.37
52phaselargerenderTemplate0.0021
488,440.70236,1990.51
53phasereadmerenderTemplate0.0019
550,886.64266,7160.51
54phasecode-heavyrenderTemplate0.0018
571,356.54277,8860.43
55phasegfmrenderTemplate0.0017
588,629.43287,1980.40
56phasesmallrenderTemplate0.0017
625,606.80302,8700.48
57phasesmallrenderHtml0.0015
699,827.50337,2490.68
58phasesmallcollect0.0012
854,835.44414,5140.43
59inlinereadmestripInlineMarkdown0.0010
974,628.99479,9800.33
60inlinereadmeparseInline0.0010
1,008,069.28499,1850.20
61phasereadmeextractFrontmatter0.0005
2,008,963.46976,0060.63
62phasecode-heavyextractFrontmatter0.0005
2,015,057.52982,3380.56
63phasesmallextractFrontmatter0.0005
2,008,434.97982,5490.32
64phaselargeextractFrontmatter0.0005
2,024,933.29982,8551.13
65phasegfmextractFrontmatter0.0005
2,017,985.09984,3060.44

Running Benchmarks

The benchmark suite lives in apps/benchmarks and writes generated reports to apps/benchmarks/results. The result folder is ignored so local benchmark history does not pollute commits.

npm run benchmark -- --fixture=small,readme,large,gfm,code-heavy --mode=parse,render --label=local
npm run benchmark:nizel -- --fixture=small,readme,large,gfm,code-heavy --mode=pipeline,phase,inline --label=local

The root benchmark scripts rebuild Nizel before running. If you run the benchmark workspace directly, rebuild Nizel first when parser or renderer code changed:

npm run build --workspace nizel