Skip to content

Performance Baselines

TopoExec keeps benchmark support lightweight and local-first. The benchmark suite helps engineers compare runtime paths on the same machine and build profile; it is not a source of global throughput or latency claims.

Run one graph case:

topoexec graph bench benchmarks/immediate_chain.yaml --steps 10 --runs 20 --format json

Generate a local baseline file:

./scripts/bench_baseline.sh

By default the script writes benchmarks/local-baseline.json, which is ignored by git. Override it with TOPOEXEC_BENCH_BASELINE_OUTPUT=/path/to/file.json. The script builds the CLI and the non-installed task-executor benchmark before collecting results.

Benchmark schema v2

Graph benchmark JSON uses schema version 2:

{
  "ok": true,
  "case": "immediate_chain",
  "runs": 20,
  "ok_runs": 20,
  "steps": 10,
  "params": {
    "file": "benchmarks/immediate_chain.yaml",
    "steps": 10,
    "runs": 20
  },
  "graph_hash": "fnv1a64:<hash>",
  "tick_calls": 800,
  "elapsed_ms": 12,
  "run_elapsed_ms": [0.6, 0.5],
  "p50_run_elapsed_ms": 0.55,
  "p95_run_elapsed_ms": 0.595,
  "p99_run_elapsed_ms": 0.599,
  "throughput_tick_calls_per_sec": 64000.0,
  "throughput_runs_per_sec": 1600.0,
  "environment": {
    "benchmark_schema": 2,
    "clock": "steady_clock",
    "runtime": "RuntimeRunner",
    "compiler": "gcc",
    "compiler_version": "13.3.0",
    "cpp_standard": "202002",
    "build_type": "RelWithDebInfo",
    "cpu_model": "example cpu",
    "cpu_threads": 20,
    "commit": "abcdef123456"
  },
  "errors": []
}

Numbers above are illustrative only.

Benchmark cases

The deterministic graph cases live in benchmarks/:

Case Runtime path covered
single_component.yaml single manually-triggered component dispatch overhead
immediate_chain.yaml immediate chain length and transaction commit overhead
fan_out.yaml multi-reader fan-out through queue/latest channels
fan_in.yaml two-source fan-in into an all_inputs join
latest_vs_queue.yaml latest and bounded queue channel policies
deferred_edges.yaml delay, state snapshot, and async admission epoch boundaries
thread_pool.yaml bounded thread_pool lane execution path
composite_loop_iterations.yaml fixed-point CompositeLoop iteration region path
payload_policies.yaml text payload copy/shared/loaned branches; no external zero-copy claim
channel_modes.yaml latest, queue, latched, previous-tick, and barrier channel modes
trigger_policies.yaml any/all/time-sync/batch/watermark/condition/debounce/rate-limit trigger paths

The task-executor benchmark is a separate non-installed CTest binary rather than a new public CLI command:

./build/topoexec_bench_task_executor --tasks 32 --runs 5 --format json

It reports deterministic and threaded task-executor completion counts and p50/p95 run summaries.

CLI path coverage remains command-smoke based rather than timing-threshold based: ctest and ./scripts/goal_check.sh bench cover graph bench, while the ordinary CLI smokes cover validate, plan, run, metrics, and trace on representative examples. Treat these as output-contract baselines, not public latency guarantees.

Local baseline workflow

./scripts/bench_baseline.sh is a convenience wrapper around scripts/bench_baseline.py. Useful environment variables:

Variable Default Purpose
TOPOEXEC_BENCH_BASELINE_OUTPUT benchmarks/local-baseline.json Output file for the newly collected baseline.
TOPOEXEC_BENCH_RUNS 5 Runs per benchmark case.
TOPOEXEC_BENCH_STEPS 10 RuntimeRunner steps per graph case.
TOPOEXEC_BENCH_TASKS 32 Task count for the task-executor case.
TOPOEXEC_BENCH_BASELINE_IN unset Optional baseline to compare against.
TOPOEXEC_BENCH_THRESHOLD_PERCENT unset Optional p95 regression threshold percentage.

Example opt-in per-machine comparison:

TOPOEXEC_BENCH_BASELINE_OUTPUT=/tmp/topoexec-current.json \
TOPOEXEC_BENCH_BASELINE_IN=benchmarks/local-baseline.json \
TOPOEXEC_BENCH_THRESHOLD_PERCENT=15 \
./scripts/bench_baseline.sh

Only use such thresholds for the same machine, compiler, build type, and workload settings. Commit the code, not the local baseline, unless a human explicitly chooses to maintain machine-specific baselines elsewhere.

CI contract

CI and ./scripts/goal_check.sh bench check benchmark output shape, required metadata, and successful execution. They do not enforce elapsed-time thresholds. This keeps normal CI stable while still catching schema drift, missing metadata, and broken benchmark workloads.