Run benchmarks#

The benchmark suite compares zodb-json-codec against Python’s pickle module on both synthetic data and real ZODB FileStorage databases.

Prerequisites#

Always benchmark against a release build. Debug builds are 5-10x slower and do not reflect production performance.

maturin develop --release

Install the benchmark dependencies:

pip install "zodb-json-codec[bench]"

Benchmark commands#

Synthetic micro-benchmarks#

Tests individual type encode/decode paths with generated data:

python benchmarks/bench.py synthetic --iterations 5000

Real FileStorage scan#

Decodes every record from an actual ZODB FileStorage file:

# Decompress the bundled sample data first
gunzip benchmarks/bench_data/Data.fs.gz

python benchmarks/bench.py filestorage benchmarks/bench_data/Data.fs

Limit the number of records with --max-records N.

PG path comparison#

Compares the two PostgreSQL decode paths – decode_zodb_record_for_pg() (returns Python dict) vs. decode_zodb_record_for_pg_json() (returns JSON string directly):

python benchmarks/bench.py pg-compare --filestorage benchmarks/bench_data/Data.fs

Combined run#

Runs both synthetic and filestorage benchmarks and exports results to JSON:

python benchmarks/bench.py all --filestorage benchmarks/bench_data/Data.fs --output results.json

Generate benchmark data#

Creates a reproducible FileStorage from Wikipedia seed data:

python benchmarks/bench.py generate

Output defaults to benchmarks/bench_data/Data.fs.

Output options#

All benchmark commands accept:

  • --output FILE – export results as JSON

  • --format {table,json,both} – output format (default: table)

PGO builds, the way the wheels are built#

The release wheels are Profile-Guided Optimization (PGO) builds, so a local PGO build reproduces what users install. PGO is not simply faster for every path: until 1.7.0 it made the PG JSON pipeline on real records slower than a build without PGO (#52, a byte loop in the JSON string writer that the profile-guided build compiled worse; the performance page has the numbers before and after the fix). Measure both builds when a change touches the storage path.

1. install LLVM tools#

rustup component add llvm-tools

2. instrumented build#

Build with profiling instrumentation enabled:

RUSTFLAGS="-Cprofile-generate=/tmp/pgo-data" maturin develop --release

3. generate profiles#

Run all three benchmark modes so the profile covers the Python-dict paths and the PG JSON path (the one zodb-pgjsonb runs; a profile without it leaves the JSON writer without counts, and the optimizer treats it as cold):

python benchmarks/bench.py synthetic --iterations 5000
python benchmarks/bench.py filestorage benchmarks/bench_data/Data.fs
python benchmarks/bench.py pg-compare --iterations 2000 --filestorage benchmarks/bench_data/Data.fs

4. merge profile data#

llvm-profdata merge -o /tmp/pgo-data/merged.profdata /tmp/pgo-data/*.profraw

5. final PGO build#

RUSTFLAGS="-Cprofile-use=/tmp/pgo-data/merged.profdata" maturin develop --release

Now re-run the benchmarks against this optimized build for production-representative numbers.

CI regression detection#

The check subcommand is designed for CI pipelines. It runs synthetic benchmarks and fails with a non-zero exit code if performance has regressed:

python benchmarks/bench.py check --iterations 500