Skip to content

Djot-PHP Performance Summary

Performance benchmarks for djot-php compared to other implementations.

Test Environment:

  • PHP 8.4.21 (OPcache enabled)
  • Node.js v22.22.2
  • Python 3.12.3
  • Rust (jotdown 0.7)
  • Go (godjot v1.0.6)
  • Linux 6.17.0-35-generic

NOTE

The djot-php tables below (size scaling, content type, safe mode, memory) were re-measured June 2026. The cross-implementation tables (PHP alternatives, cross-language, profiles) are pending a re-measurement on an unloaded machine and still show the previous figures.

Quick Reference

Default source-to-HTML conversion now includes a conservative borrowed-source route for common documents up to 64 KiB. On PHP 8.5.9 with CLI OPcache, a 57,410-byte heading/paragraph/core-inline fixture measured 6.5 ms median versus 49.5 ms through an explicitly configured owned-AST converter (7.7x faster), with byte-identical output. Absolute timings remain machine-dependent.

The cross-language runners now generate the same paragraph-safe medium fixture in every runtime. A 20-iteration/5-warmup run over its 51,179 bytes produced:

Djot implementationMedianThroughputRelative to PHP
Rust jotdown 0.71.20 ms40.5 MB/s6.22x faster
PHP djot-php7.46 ms6.5 MB/sbaseline
JavaScript @djot/djot 0.310.99 ms4.4 MB/s1.47x slower
Go godjot 1.0.617.47 ms2.9 MB/s2.34x slower

Environment: PHP 8.5.9, Node.js 22.22.2, Rust 1.97.1, and Go 1.22.2. Run node tests/benchmark/compare-languages.mjs --djot-only --iterations=20 --warmup=5 from the repository root to reproduce it.

Unsupported or ambiguous input automatically falls back to the complete parser and renderer. Calling parse(), selecting another renderer, or configuring profiles, safety, extensions, listeners, source lines, or output behavior also keeps the authoritative path.

Document SizePHP FullThroughput
1 KB0.51 ms~2.1 MB/s
11 KB5.2 ms~2.1 MB/s
56 KB26 ms~2.2 MB/s
225 KB118 ms~1.9 MB/s
1 MB605 ms~1.9 MB/s

PHP Alternatives Comparison

With equivalent features enabled (tables, footnotes, smart typography):

Library30KB DocThroughputvs djot-php
erusev/parsedown1.69 ms16.0 MB/s6.7x faster
michelf/php-markdown5.16 ms5.2 MB/s2.2x faster
michelf/php-markdown (Extra)6.15 ms4.4 MB/s1.9x faster
djot-php11.36 ms2.6 MB/sbaseline
league/commonmark (GFM)15.00 ms1.8 MB/s1.3x slower
league/commonmark15.31 ms1.8 MB/s1.4x slower
league/commonmark (Full)23.54 ms1.3 MB/s2.1x slower

Key finding: djot-php is 2x faster than CommonMark when both have equivalent features (tables, footnotes, smart punct) enabled.

Cross-Language Comparison

Benchmarked on medium-sized documents (~56 KB):

ImplementationMean TimeThroughputvs PHP
Rust (jotdown)~1-2 ms~30+ MB/s~12x faster
Go (godjot)~2-4 ms~15+ MB/s~6x faster
JS (@djot/djot)8.1 ms5.2 MB/s2.8x faster
PHP (djot-php)23 ms2.4 MB/sbaseline
Python-Markdown*41.1 ms1.0 MB/s1.8x slower

*Python libraries are Markdown parsers (no Djot implementation exists for Python).

Document Size Scaling

PHP djot-php scales linearly with document size:

SizeInputMedian TimeThroughput
tiny1.1 KB0.51 ms2.1 MB/s
small11.1 KB5.24 ms2.1 MB/s
medium56.1 KB25.92 ms2.2 MB/s
large225.5 KB118.09 ms1.9 MB/s
huge1.1 MB605.36 ms1.9 MB/s

Content Type Performance

Different content types have varying performance characteristics:

Content TypeSizeMedian TimeThroughputNotes
code_heavy5.8 KB1.74 ms3.4 MB/sFastest - simple parsing
nested_lists5.6 KB3.14 ms1.8 MB/sAverage
tables9.2 KB5.13 ms1.4 MB/sTable parsing overhead
complex8.9 KB5.28 ms1.7 MB/sMany features
inline_heavy14.8 KB9.16 ms1.3 MB/sMany inline elements

Code-heavy documents are fastest because code blocks require minimal parsing.

Profile Performance

Different profiles have similar performance since they filter the same AST:

ProfileMean TimeNotes
none20.70 msNo filtering
full22.10 msAll features enabled
article21.80 msBlog/article content
comment23.27 msUser comments
minimal22.90 msBasic text formatting

Safe Mode

Safe mode has negligible performance impact:

ModeMedian Time
Disabled24.88 ms
Enabled25.04 ms

Config Lever Cost: blocksInterruptParagraphs

The blocksInterruptParagraphs lever lets a top-level block element interrupt a paragraph without a blank line in front of it (markdown-style). Its cost depends entirely on whether a document actually triggers that path. Relative A/B on equivalent ~50 KB documents:

Document shapevs default
Plain prose (paragraphs separated by blank lines)no measurable change
Block abutting every paragraph (no blank line)~50% slower

Enabling the lever is effectively free for normal documents: when no block ever abuts a paragraph, the extra check costs nothing measurable. The overhead only appears on the markdown-style content the lever exists for, where each interruption splits one paragraph into a paragraph plus a separate block and therefore parses and builds more nodes. The slowdown is proportional to how often blocks abut paragraphs, not a fixed tax.

Memory Usage

Memory scales approximately linearly with document size:

Input SizePeak MemoryRatio
11 KB66 MB~6100x
57 KB66 MB~1200x
226 KB68 MB~310x
1 MB~80 MB~80x

Note: PHP has a base memory overhead. The incremental memory per input byte is approximately 30-45x.

Running Benchmarks

bash
# Internal PHP benchmark
php tests/benchmark/benchmark.php

# PHP alternatives comparison
cd tests/benchmark_alternatives
composer install
php benchmark.php

# Cross-language comparison
./tests/benchmark/run-all.sh --compare

Key Takeaways

  1. Throughput: PHP djot-php processes ~1.9-2.2 MB/s of djot content (with OPcache)
  2. vs CommonMark (Full): djot-php is 2x faster with equivalent features
  3. vs Parsedown: Parsedown is 6-7x faster but lacks advanced features
  4. Scaling: Performance scales linearly with document size (O(n))
  5. vs Rust/Go: Native implementations are 6-12x faster (as expected)
  6. vs JavaScript: Reference JS implementation is ~3x faster
  7. Safe mode: No significant performance penalty
  8. OPcache: Enable OPcache for best performance (~2x improvement)

Released under the MIT License.