Benchmarks
Purpose
Galley has more than one benchmark route because it can answer more than one performance question.
The main split is:
- Parser-only throughput This tries to measure the parser itself with as little wrapper noise as possible.
- Executable throughput This measures the full generated executable, including its CLI path and executable-level layout effects.
Use parser-only numbers for parser comparisons. Use executable numbers when you care about the real shipped binary as a whole.
See Benchmark Layout Findings for the code-layout issue behind this split.
Parser-only Throughput
This route benchmarks the generated parser API directly. It reuses one Session, reads the input once into sentinel-terminated memory, and times Session.parseSentinelBytes.
Use it when:
- you want raw parser throughput
- you are comparing parser versions
- you are comparing Galley to other parser libraries
Run it like this:
./zig-out/bin/galley --parser-type ll --no-ast --no-error-recovery --input-size 32 languages/json
zig build -Doptimize=ReleaseFast run-api-bench-ll-json -- languages/json/samples/code-02.json --iterations 100 --warmup-iterations 10The 32-bit input size is required because code-02.json is larger than the default 16-bit cursor range.
General form:
zig build -Doptimize=ReleaseFast run-api-bench-<parser>-<language> -- <file> --iterations <n> --warmup-iterations <m>AST Memory Usage
An opt-in API benchmark can inspect AST allocator usage without recording timing. Generate an AST-enabled parser, then build the normal API benchmark target with the compile-time instrumentation enabled:
./zig-out/bin/galley --parser-type ll --with-ast --no-procedures languages/json
zig build -Dast-memory-benchmark=true run-api-bench-ll-json -- languages/json/samples/code-01.jsonIt parses once and reports reachable nodes, the final and peak allocator counters, total node creations, sparsity, and pool capacity/utilization. The instrumentation is compiled out by default; --iterations and --warmup-iterations are intentionally unavailable in this mode.
Executable Throughput
This route benchmarks the normal generated executable. It includes the CLI path and executable-level layout effects.
Use it when:
- you care about the real shipped binary
- you want end-to-end executable timing
- you suspect an executable-level change affected throughput
Run it like this:
./zig-out/bin/galley --parser-type ll --no-ast --no-error-recovery --input-size 32 languages/json
zig build -Doptimize=ReleaseFast run-ll-json -- languages/json/samples/code-02.json --iterations 100 --warmup-iterations 10General form:
zig build -Doptimize=ReleaseFast run-<parser>-<language> -- <file> --iterations <n> --warmup-iterations <m>Benchmark Suite And Result Generation
This route is the repository’s benchmark pipeline. scripts/benchmark.py generates raw result files under benchmark_results/, and scripts/generate_benchmarks_doc.py turns those files into the published benchmark markdown.
Use it when:
- you want repository benchmark result files
- you want a repeatable scripted benchmark sweep
- you want to refresh the published benchmark document
Run it like this:
python3 scripts/benchmark.py --language json --parser-type LL --no-ast --input-size 16 --no-ast-for-terminalsIf you intentionally want executable-level suite results instead:
python3 scripts/benchmark.py --route cli --language json --parser-type LL --no-ast --input-size 16 --no-ast-for-terminalsAfter collecting fresh result files, regenerate the benchmark markdown like this:
python3 scripts/generate_benchmarks_doc.pyValidation Without Benchmarking
This is for correctness checks, not throughput reporting.
Use it when:
- you only care that parsing still works
- you want the main repository test suite
Run them like this:
zig build testReading Benchmark Output
Galley benchmark outputs report:
- Parsed bytes Total bytes parsed across timed iterations.
- Duration Total timed duration.
- Throughput Parsed bytes divided by duration.
- Nodes allocated AST node allocations during the run.
Interpretation depends on route:
- parser-only route means these numbers are intended to represent parser capacity
- executable route means these numbers represent the full generated executable path
Measurement Rules
Always benchmark with:
-Doptimize=ReleaseFastPractical rules:
- Use warmup iterations.
- Use inputs large enough to drown timer noise.
- Avoid background CPU noise during comparisons.
- Compare parser-only with parser-only, and executable with executable.