Performance
Fatou is a compiled Rust formatter competing with two Julia-native tools, Runic and JuliaFormatter. This page compares their formatting throughput.
Methodology
We measure each tool in a warm loop: the tool is loaded once, run through a
few warmup calls, and then timed over many iterations. This deliberately
excludes process startup and first-call JIT compilation for the Julia tools,
which would otherwise dominate and obscure the actual formatting cost. In other
words, these numbers reflect a long-lived editor or language-server session, not
the cold julia -e ... command-line invocation. That cold path is measured
separately in Cold start below.
Because each tool runs in its own runtime, we report throughput in MB/s, which normalizes for byte count and stays comparable even when tools cover different files. Each tool formats with its own default style; we are measuring speed, not comparing output. A file counts for a tool only if that tool formats it without error, and any skips are reported.
Corpora
Two real-world projects, both pinned to a tag, picked to pull in opposite directions:
- JuliaSyntax.jl, the parser Fatou targets for parity: dense branching, large token tables, and the code Fatou is best equipped to handle. Home turf.
- DataFrames.jl, ordinary library code of the kind users actually format: docstring-heavy, macro-heavy, built around a large indexing DSL, and roughly 2.6x the size of the JuliaSyntax tree.
Scenarios
- Single file (three of them), through each tool’s pure
String -> Stringformatter (fatou::formatter::format,Runic.format_string,JuliaFormatter.format_text). The three targets span both size and shape:parse_stream.jl(42 KB of dense parser internals),kinds.jl(24 KB that is almost entirely one flat macro/data table), andabstractdataframe.jl(100 KB of docstring- and macro-heavy application code). - Project (one per corpus): the whole
src/tree, driven through each tool’s own directory entry point, so file discovery, IO, and the tool’s internal parallel scheduling all count. This is the “format my whole project” path. Fatou usesfatou::formatter::check_paths(glob/directory discovery plus rayon-parallel formatting, read-only); JuliaFormatter usesformat(dir; overwrite = false)(recursive, thread-parallel, read-only). Runic is excluded from these scenarios by design: it has no in-process directory API (itsformat_fileis single-file only, and directory walking lives solely in its CLI), so there is nothing to measure on the same terms.
Reproduce with task bench (after reloading the devenv shell so Runic is on
the Julia path). Results are written to bench/results.json.
Setup
- Corpora: JuliaSyntax.jl
v0.4.10(09576ca), DataFrames.jlv1.8.2(946c72a) - Versions: Fatou
0.10.0, Runic1.5.1, JuliaFormatter2.4.0, Julia1.12.6 - Host: AMD Ryzen 9 7900 12-Core Processor (Linux x86_64)
- Machine:
terra - Warm-loop iterations: 50 single, 20 project; 3 warmup
- Cold-start iterations: 5 fresh-process runs (single file)
Results
Single files and whole projects get a chart each: they measure different work at different sizes, and sharing one axis buries that. In both, Fatou is the baseline on the dashed line at 1, and every other tool’s time is plotted relative to it, so faster tools fall below the line and slower tools rise above it.
Single files
String -> String formatter, in the tool's own default style. Hover a dot for the exact figures.Data table
parse_stream.jl (JuliaSyntax/src/parse_stream.jl)
| Tool | Files | Bytes | Median (ms) | Throughput (MB/s) | Relative |
|---|---|---|---|---|---|
| Fatou | 1 | 41,937 | 6.2 | 6.75 | baseline |
| Runic | 1 | 41,937 | 26.5 | 1.58 | 4.27x |
| JuliaFormatter | 1 | 41,937 | 9.3 | 4.49 | 1.50x |
kinds.jl (JuliaSyntax/src/kinds.jl)
| Tool | Files | Bytes | Median (ms) | Throughput (MB/s) | Relative |
|---|---|---|---|---|---|
| Fatou | 1 | 24,442 | 3.0 | 8.23 | baseline |
| Runic | 1 | 24,442 | 17.7 | 1.38 | 5.97x |
| JuliaFormatter | 1 | 24,442 | 3.7 | 6.64 | 1.24x |
abstractdataframe.jl (DataFrames/src/abstractdataframe/abstractdataframe.jl)
| Tool | Files | Bytes | Median (ms) | Throughput (MB/s) | Relative |
|---|---|---|---|---|---|
| Fatou | 1 | 100,388 | 8.2 | 12.30 | baseline |
| Runic | 1 | 100,388 | 31.9 | 3.15 | 3.91x |
| JuliaFormatter | 1 | 100,388 | 12.2 | 8.22 | 1.50x |
Projects
Runic is absent because it has no in-process directory API. Hover a dot for the exact figures.Data table
JuliaSyntax (JuliaSyntax/src)
| Tool | Files | Bytes | Median (ms) | Throughput (MB/s) | Relative |
|---|---|---|---|---|---|
| Fatou | 15 | 332,783 | 17.6 | 18.86 | baseline |
| JuliaFormatter | 14 | 332,783 | 23.4 | 14.25 | 1.32x |
JuliaFormatter skipped parser.jl: processed, but the tool's own output failed its parse check, so the file was left unchanged
DataFrames (DataFrames/src)
| Tool | Files | Bytes | Median (ms) | Throughput (MB/s) | Relative |
|---|---|---|---|---|---|
| Fatou | 36 | 870,581 | 15.1 | 57.80 | baseline |
| JuliaFormatter | 36 | 870,581 | 37.2 | 23.38 | 2.47x |
Cold start
The warm loop above is the right model for an editor or language server that
stays resident, but it hides the cost a command-line user pays on the very first
run. This section measures that cold start directly: each tool is invoked as
a fresh process that starts up, formats the single file once, and exits. For the
Julia tools that means paying Julia’s startup, package loading, and first-call
JIT compilation every time, through the same julia -e 'using ...' path a shell
user would take; Fatou, a compiled binary, pays only process startup through
fatou format. Only one file (parse_stream.jl) is measured, since the numbers
are dominated by fixed startup and compilation cost, not by the file’s size.
fatou format; the Julia tools run through the same julia -e 'using ...' path a shell user takes, so Julia startup, package load, and first-call compilation all count. Hover a dot for the exact figures.Data table
| Tool | Median time | Throughput (MB/s) | vs Fatou |
|---|---|---|---|
| Fatou | 7.8 ms | 5.38 | baseline |
| Runic | 192.0 ms | 0.22 | 24.64x |
| JuliaFormatter | 237.7 ms | 0.18 | 30.50x |