Performance

This page presents performance benchmarks for Panache, comparing its formatting and linting speed against popular alternatives like Prettier, Pandoc, rumdl, mdformat, Yamark, mado, markdownlint, and markdownlint-cli2 on real Quarto and Markdown documents, and its language-server latency, runtime, and memory use against Marksman and q2. The benchmarks highlight Panache’s efficiency and suitability for editor and repository-wide work.

Overview

Panache is designed for speed without compromising on correctness. Built in Rust and compiled to native code, it delivers fast formatting with low startup overhead. In this document, we present benchmarks comparing Panache’s formatting and linting performance against popular alternatives like Prettier, Pandoc, rumdl, mdformat, Yamark, mado, markdownlint, and markdownlint-cli2 on a realistic corpus of Quarto and Markdown documents. R Markdown is outside the current benchmark corpus.

The formatting and linting benchmarks have two suites: per-document benchmarks that run each formatter on each document in the corpus individually, and repository-wide benchmarks that run formatters on entire repositories of tracked documents. The former highlights raw formatting speed on a variety of real-world documents; the latter captures the overhead of processing multiple files in a single run, which is more representative of repository-wide formatting or linting. CLI caches are disabled for these comparisons. A separate language-server suite measures startup, warm request latency, edit runtime, and memory use during an editor session.

Yamark runs with an empty configuration and external code formatters disabled (--config /dev/null --skip-embedded-formatters). Its default wrapping and style settings apply. Tools differ in supported syntax and formatting policy, so these timings compare their respective workloads. Failed repository runs have null timings in the data and are excluded from the plots.

Yamark’s formatting path uses source spans and formatting plans and preserves display math as opaque content. Panache builds the lossless syntax tree also used by its linter and language server, and parses and formats TeX math, which explains part of the gap.

The numbers on this page are produced by the scripts in benches/ and read from the JSON files written next to this document. The benchmark chunks on this page are intentionally not executed during preview or render, so small content edits reuse the existing JSON files instead of rerunning the benchmarks. Refresh the benchmark data explicitly with the commands at the bottom of this page, then delete docs/_freeze/guide/performance/ and re-render if you want the page to pick up newly generated results.

Formatting

Single-Document

In Figure 1, we compare formatting time per document across panache, Prettier, Pandoc, rumdl, mdformat, and Yamark. Each dot is one document; the y-axis shows time relative to panache (×, lower is faster). Panache sits at 1× by construction (dashed baseline); points above it are slower. Hover a point to see the absolute wall-clock time in milliseconds. Formatters are ordered left-to-right from fastest to slowest on average.

Figure 1: Formatting time per document, comparing panache to Prettier, Pandoc, rumdl, mdformat, and Yamark. Each dot is one document; the y-axis shows time relative to panache (×, lower is faster).

Repository-Wide

In Figure 2, we compare formatting time across panache, Prettier, rumdl, mdformat, and Yamark on tracked Markdown files from several repositories.

Figure 2: Repository-wide formatting benchmarks on standard Markdown repos, comparing panache to Prettier, rumdl, mdformat, and Yamark. Each dot is one repo/tool pair; the y-axis shows time relative to panache within that repo (×, lower is faster).

In Figure 3, we compare formatting time across panache, rumdl, and Yamark on tracked .qmd files from several Quarto repositories.

Figure 3: Repository-wide formatting benchmarks on Quarto repos. Each dot is one repo/tool pair; the y-axis shows time relative to panache within that repo (×, lower is faster).

Each dot is one repo/tool pair. Panache sits at 1× by construction (dashed baseline), and points above it are slower on that repository. Hover a point to see the absolute wall-clock time in milliseconds and the corpus size.

Linting

Single-Document

In Figure 4, we compare linting time per document across panache lint, rumdl check, mado check, markdownlint, and markdownlint-cli2. Each dot is one document; the y-axis shows time relative to panache lint (×, lower is faster). Panache sits at 1× by construction.

Figure 4: Linting time per document, comparing panache lint to rumdl check, mado check, markdownlint, and markdownlint-cli2. Each dot is one document; the y-axis shows time relative to panache lint (×, lower is faster).

Repository-Wide

This suite benchmarks linting on the same standard Markdown repositories as the formatting comparison, using panache lint, rumdl check, mado check, markdownlint, and markdownlint-cli2. In Figure 5, each dot is one repo/tool pair; the y-axis shows time relative to panache lint within that repo (×, lower is faster).

Figure 5: Repository-wide linting benchmarks on standard Markdown repos, comparing panache lint to rumdl check, mado check, markdownlint, and markdownlint-cli2. Each dot is one repo/tool pair; the y-axis shows time relative to panache lint within that repo (×, lower is faster).

In Figure 6, we compare linting time across panache and rumdl on tracked .qmd files from several Quarto repositories.

Figure 6: Repository-wide linting benchmarks on Quarto repos. Each dot is one repo/tool pair; the y-axis shows time relative to panache lint within that repo (×, lower is faster).

Language-Server Speed and Memory

This Linux-only benchmark compares the latency, runtime, and memory use of Panache and Marksman. It opens the five largest tracked Markdown files in a pinned revision of the Rust Book, requests diagnostics and navigation features, and then performs 1,000 fixed-width reference-label renames. Each rename is followed by a definition request, which forces the server to observe the updated label index.

The runner starts three fresh processes per server in alternating order. Each session opens the same files, obtains diagnostics using the server’s advertised pull or push mode, primes navigation requests, and waits for background work to settle. It then times warm requests and the edit workload. The committed JSON contains each run’s measurements; rendering this page never runs the benchmark.

Startup and Edit Runtime

Figure 7 shows four elapsed times in milliseconds. Initialize runs from process launch to the initialize response. Workspace ready runs from launch to the start of the final quiet window after initialization. Open files ready runs from the burst of file-open notifications through the initial diagnostics and navigation requests to the next quiet window. Edit workload times all reference-label edits and their definition responses, excluding the final diagnostics and memory-settling wait.

Readiness is estimated from process-tree CPU samples every 150 ms. A quiet window stays below 5% of one CPU core for five seconds; its start is reported only after those five seconds have elapsed. This excludes the deliberate wait from the plotted readiness times, but the estimate still has sampling error.

Figure 7: Language-server startup and edit runtime on a logarithmic scale. Each row compares Panache and Marksman. Dots show medians; lines span the minimum and maximum across fresh processes. Lower is faster.

Request Latency

Figure 8 shows the median and 95th percentile (p95) of serial request round trips over stdio. Each target gets two unmeasured warmup rounds and 20 measured rounds per fresh process. Percentiles pool the measured samples from all three processes. Document-symbol requests cover the three largest open files. Hover, definition, references, and rename use the reference link appended to the largest file. References include the declaration; rename returns edits without applying them.

Edit to definition measures each change notification through its following definition response, including the work needed to observe the edited document. It pools all 1,000 edits per process and has no discarded warmups. Tooltips show sample counts, empty responses, returned item counts, files involved, and result sizes. These help explain differences in the work each server performs. A failed or timed-out request fails the benchmark. Points marked “empty” returned no items in any measured request; they measure the overhead of responding without a result.

Figure 8: Language-server request latency on a logarithmic scale. Each row compares Panache and Marksman. Dots show medians; lines extend to p95 and are not confidence intervals. Lower is faster.

Memory

The runner samples the complete process tree’s resident set size (RSS) and proportional set size (PSS) from /proc every 150 ms. RSS counts shared pages in each process; PSS divides them among the processes that map them. Each milestone follows a quiet window, except peak, which covers the whole session. Figure 9 shows median RSS across the three processes, with PSS in the tooltips.

Figure 9: Median whole-process-tree RSS during the language-server workload. Each milestone is measured after quiescence, except peak, which is the largest 150 ms sample from the complete session.

The comparison measures default architecture, not identical internal work. Marksman indexes the Markdown workspace eagerly, while Panache analyzes open documents and discovers project relationships lazily. Both servers receive the same open, edit, and navigation sequence, while diagnostics use each server’s advertised pull or push mode. Their memory totals reflect those different strategies.

Quarto Language Server

This separate Linux benchmark compares Panache with q2 lsp, the experimental Rust Quarto language server. It uses three authoring guides from a pinned q2 revision: computations, Markdown basics, and title blocks. The files live in a standalone project with no workspace index to discover. Both servers must accept every document without error diagnostics and return a nonempty outline.

Each run opens the files serially, measures document-symbol requests, and performs 100 full-document replacements that change an appended heading in the computations guide. Full replacements accommodate q2’s synchronization mode. A final outline request must contain the last edited heading. This track exercises diagnostics and symbols; q2 does not implement the navigation features used in the Markdown comparison above.

Panache uses pull diagnostics, with a request immediately after each open or change. q2 pushes diagnostics after processing the notification. The timings include those different delivery paths. They compare editor-visible latency under these client policies, with each server’s own checks, and do not measure Panache’s debounced push mode. Empty diagnostic sets are successful analyses; empty symbol results, protocol errors, and timeouts fail the benchmark.

Figure 10 pools samples across the three processes. File opens have no warmups. Symbol requests have two warmup rounds and 20 measured rounds per file per process. Each edit waits for diagnostics before the next begins.

Figure 10: Quarto LSP latency. Dots show medians; lines extend to p95. Tooltips include sample counts and the median number of returned diagnostics or symbols.

Initialization measures process launch through the initialize response. Memory uses the same process-tree sampler and five-second quiet windows as the Markdown track. q2 performs open-document parsing and outline analysis; Panache also retains its syntax trees and analysis database. The table reports medians across processes. Neither server builds a whole-project index in this standalone workload.

Reproducing

All benchmarks are reproducible. The formatting and linting comparisons require hyperfine; the language-server memory and speed comparisons require Linux with /proc. The Markdown track needs Marksman; the Quarto track downloads a pinned q2 release and verifies its SHA-256 hash, or uses the executable specified by Q2_BIN. The scripts in benches/ are designed to be run from the repository root:

# Download test documents (idempotent)
cd benches/documents && ./download.sh && cd ../..

# Outside devenv: install Yamark and record its version
# The development environment already provides both.
uv tool install yamark==0.3.0
export PANACHE_BENCH_YAMARK_VERSION=0.3.0

# Run comparison benchmark and write JSON
bash benches/compare_all.sh --json --out docs/guide/performance_data.json

# Run repository-wide Markdown formatting benchmark
bash benches/compare_repo_suite.sh --mode format --track markdown --out docs/guide/performance_repo_markdown_format_data.json

# Run repository-wide Quarto formatting benchmark
bash benches/compare_repo_suite.sh --mode format --track quarto --out docs/guide/performance_repo_quarto_format_data.json

# Run repository-wide Markdown lint benchmark
bash benches/compare_repo_suite.sh --mode lint --track markdown --out docs/guide/performance_repo_markdown_lint_data.json

# Run repository-wide Quarto lint benchmark
bash benches/compare_repo_suite.sh --mode lint --track quarto --out docs/guide/performance_repo_quarto_lint_data.json

# Run per-document lint benchmark
bash benches/compare_lint_single.sh --out docs/guide/performance_lint_single_data.json

# Run the Linux language-server speed and memory benchmark
# bench:lsp-memory remains an alias.
task bench:lsp

# Run the Quarto diagnostics, symbols, and memory comparison
task bench:lsp-quarto

# Or run the human-readable text variant
bash benches/compare_all.sh

# Re-render this page (uses freeze cache by default)
quarto render docs/guide/performance.qmd

# Pick up refreshed JSON by invalidating the rendered page's freeze cache
rm -rf docs/_freeze/guide/performance
quarto render docs/guide/performance.qmd