| name | benchmarking |
| description | Run and manage performance benchmarks with cargo xtask bench for facet-json, analyzing results with Markdown reports and comparing against serde_json baseline |
Benchmarking with cargo xtask bench
The facet project uses a sophisticated benchmarking system that generates Markdown reports comparing performance across multiple targets.
Quick Reference - Running Specific Benchmarks
cargo bench --bench unified_benchmarks_divan -- flatten_2enums
FACET_TIER2_DIAG=1 cargo bench --bench unified_benchmarks_divan -- flatten_2enums 2>&1 | grep TIER_DIAG
cargo bench --bench unified_benchmarks_divan -- flatten_2enums 2>&1 | grep TIER_STATS
cargo bench --bench unified_benchmarks_divan -- flatten
cargo bench --bench unified_benchmarks_divan -- "tier2"
cargo bench --bench unified_benchmarks_divan -- --list | grep -v " " | head -20
โ ๏ธ IMPORTANT: Benchmark .rs files are GENERATED from facet-json/benches/benchmarks.kdl.
DO NOT edit unified_benchmarks_*.rs directly - edit benchmarks.kdl instead.
Quick Usage
cargo xtask bench --index --serve
cargo xtask bench
cargo xtask bench --no-run
cargo xtask bench --index booleans
cargo xtask bench --no-run --index --serve
How It Works
The benchmarking system has three main components:
1. Benchmark Definition (benchmarks.kdl)
Benchmarks are defined in facet-json/benches/benchmarks.kdl using KDL syntax:
benchmark name="simple_struct" type="SimpleRecord" category="micro" {
json "{\"id\": 42, \"name\": \"test\", \"active\": true}"
}
benchmark name="booleans" type="Vec<bool>" category="synthetic" {
generated "booleans"
}
type_def name="SimpleRecord" {
code """
#[derive(Debug, PartialEq, Facet, serde::Serialize, serde::Deserialize, Clone)]
struct SimpleRecord {
id: u64,
name: String,
active: bool,
}
"""
}
Categories: micro, synthetic, realistic, other
Data sources: json (inline), json_file, json_brotli, generated
2. Benchmark Generation (cargo xtask gen-benchmarks)
Run this after editing benchmarks.kdl:
cargo xtask gen-benchmarks
This generates three files in facet-json/:
benches/unified_benchmarks_divan.rs - Wall-clock timing benchmarks
benches/unified_benchmarks_gungraun.rs - Instruction count benchmarks
tests/generated_benchmark_tests.rs - Test versions for valgrind debugging
Every benchmark gets all 4 targets automatically:
serde_json - Baseline (serde_json crate)
facet_format_json - facet-format-json without JIT (reflection only)
facet_format_jit_t1 - Tier-1 JIT (shape-based, ParseEvent stream)
facet_format_jit_t2 - Tier-2 JIT (format-specific, direct byte parsing)
3. Benchmark Execution and Analysis
cargo xtask bench does:
- Runs
unified_benchmarks_divan (wall-clock times via divan)
- Runs
unified_benchmarks_gungraun (instruction counts via gungraun + valgrind)
- Parses output and combines results
- Generates multiple report formats:
bench-reports/run.json - Full structured data (schema: run-v1)
bench-reports/perf/RESULTS.md - Markdown report for LLMs and humans
bench-reports/perf-data.json - Legacy format for perf tracking
The Markdown Report (perf/RESULTS.md)
Located at bench-reports/perf/RESULTS.md, this is the authoritative source for performance analysis:
Structure:
- Targets table - Definitions of all benchmark targets
- Benchmark sections - Grouped by category (Micro, Synthetic, Realistic)
- Per-benchmark tables - Deserialize and Serialize results
- Columns: Target, Time (median), Instructions, vs serde_json ratio
- Ratios:
**0.84ร** โ (wins), 1.03ร (close), 3.12ร โ (needs work)
- Summary - Auto-categorized by performance:
- Wins: โค1.0ร vs serde_json
- Close: โค1.5ร vs serde_json
- Needs Work: >1.5ร vs serde_json
Example:
### booleans
**Deserialize:**
| Target | Time (median) | Instructions | vs serde_json |
|--------|---------------|--------------|---------------|
| serde_json | 56.21ยตs | 1,157,922 | 1.00ร |
| format+jit2 | 53.46ยตs | 972,221 | **0.84ร** โ |
| format+jit1 | 809.30ยตs | 7,031,459 | 6.07ร โ |
| format | 2.94ms | 23,169,951 | 20.01ร โ |
Adding New Benchmarks
-
Edit facet-json/benches/benchmarks.kdl
benchmark name="my_bench" type="MyType" category="synthetic" {
generated "my_generator"
}
type_def name="MyType" {
code """
#[derive(Debug, Facet, serde::Serialize, serde::Deserialize, Clone)]
struct MyType {
field: String,
}
"""
}
-
If using generated, add generator to tools/benchmark-generator/src/main.rs
- Edit
generate_json_data() function
- Add case for your generator name
-
Regenerate benchmarks
cargo xtask gen-benchmarks
-
Run benchmarks
cargo xtask bench --index --serve
Important Flags
--no-run
Skips running benchmarks, uses latest data. Useful for:
- Regenerating reports after fixing parser bugs
- Testing report generation changes
- Quick iterations on report formatting
--index
Generates the full perf.facet.rs index:
- Clones the
facet-rs/perf.facet.rs repo (gh-pages branch)
- Copies benchmark reports to
bench-reports/perf/
- Generates index.html and supporting files
- Required for viewing the interactive SPA
--serve
Starts a local server at http://localhost:1999 to view reports.
Requires --index.
--push
Pushes generated reports to the perf.facet.rs repo.
Use with caution - only for publishing official results.
Debugging Benchmarks with Valgrind
The generated tests in tests/generated_benchmark_tests.rs mirror the benchmarks and can be run under valgrind:
cargo nextest run --profile valgrind -p facet-json generated_benchmark_tests::test_booleans --features jit
cargo nextest run --profile valgrind -p facet-json test_simple_struct --features jit
This is essential for debugging crashes or memory issues in benchmarks.
Files and Directories
bench-reports/
โโโ divan-{timestamp}.txt # Raw divan output
โโโ gungraun-{timestamp}.txt # Raw gungraun output
โโโ run.json # Structured results (run-v1 schema)
โโโ perf-data.json # Legacy perf tracking format
โโโ perf/
โโโ RESULTS.md # **MAIN REPORT - READ THIS**
โโโ index.html # SPA (generated with --index)
โโโ app.js # SPA logic (copied from scripts/)
โโโ shared-styles.css # SPA styles (copied from scripts/)
facet-json/benches/
โโโ benchmarks.kdl # **EDIT THIS to add benchmarks**
โโโ unified_benchmarks_divan.rs # Generated (divan)
โโโ unified_benchmarks_gungraun.rs # Generated (gungraun)
facet-json/tests/
โโโ generated_benchmark_tests.rs # Generated (for valgrind)
tools/
โโโ benchmark-generator/ # KDL โ Rust codegen
โโโ benchmark-analyzer/ # Output parsing + report generation
Don't Edit Generated Files
โ NEVER edit these files (they're regenerated):
unified_benchmarks_divan.rs
unified_benchmarks_gungraun.rs
generated_benchmark_tests.rs
bench-reports/perf/index.html, app.js, shared-styles.css
โ
Edit these instead:
facet-json/benches/benchmarks.kdl - Benchmark definitions
tools/benchmark-generator/src/main.rs - Generator logic (for generated benchmarks)
scripts/app.js, scripts/shared-styles.css - SPA source (not the copies in perf/)
Common Workflows
Quick local benchmark run
cargo xtask bench
Full interactive report
cargo xtask bench --index --serve
After editing benchmarks.kdl
cargo xtask gen-benchmarks
cargo xtask bench
Re-analyze existing data
cargo xtask bench --no-run --index
Benchmark a specific test
cargo xtask bench integers
Performance Analysis Tips
-
Focus on the Markdown report first (perf/RESULTS.md)
- Easy to grep, parse, and read
- Shows all critical metrics in one place
- Auto-categorized by performance tier
-
Use instruction counts, not just time
- More stable than wall-clock time
- Architecture-independent
- Appears in "vs serde_json" column when available
-
Look for patterns in the Summary section
- "Needs Work" items are optimization targets
- "Wins" validate current approach
- "Close" items are low-hanging fruit
-
Compare Tier-1 vs Tier-2 JIT
- Large gaps = Tier-2 not implemented or buggy
- Similar performance = Tier-2 working but not optimized
- Tier-2 wins = format-specific optimizations paying off
Troubleshooting
Benchmarks fail to compile
cargo xtask gen-benchmarks
Parser errors in output
- Check
bench-reports/divan-*.txt or gungraun-*.txt for malformed output
- Fix the benchmark code, not the parser (usually)
Missing benchmarks in report
- Ensure benchmark has
category in benchmarks.kdl
- Check that
cargo xtask gen-benchmarks ran successfully
- Verify benchmark functions are generated (check
unified_benchmarks_*.rs)
--index fails
- Ensure
gh CLI is installed and authenticated
- Check network connection (clones from GitHub)
- Try
--index without --push first
See Also
- divan docs: https://docs.rs/divan/
- gungraun: Custom fork with valgrind integration
- Nextest valgrind profile:
.config/nextest.toml
- Benchmark generator:
tools/benchmark-generator/
- Report analyzer:
tools/benchmark-analyzer/