Performance Measurement
FutureSDR performance work usually happens at two levels:
- use Mocker to benchmark one block implementation,
- use the
perf/applications to benchmark complete flowgraphs and scheduler or buffer configurations.
Always measure release builds. Debug builds are useful while developing, but they do not represent runtime performance.
Block Microbenchmarks
For a single block, use Mocker. It runs the block directly, without a scheduler and without a full Flowgraph, so the benchmark mostly measures the block’s work() implementation and the buffer operations it performs.
This is the right tool for comparing implementation choices inside one block, checking how performance changes with input size, or writing a Criterion benchmark around a small processing kernel. The repository’s apply benchmark is a compact example:
cargo bench --bench apply
Mocker is not a replacement for full-flowgraph benchmarks. It intentionally removes scheduling, message routing between blocks, and end-to-end stream topology effects.
Parameter Sweeps
The perf/ directory contains standalone benchmark applications for measuring complete configurations. These examples are useful when the question is about scheduler choice, buffer behavior, number of stages, number of pipes, sample counts, or other flowgraph-level parameters.
Many of the directories contain a Makefile that iterates over a parameter grid, writes CSV files to perf-data/, and provides helper targets for selected configurations:
cd perf/null
make
Inspect the local Makefile before running a sweep. Some benchmarks run for a long time, and the parameter ranges are intentionally broad.
Profiling One Configuration
To understand where time is spent in one specific configuration, profile that configuration directly. Samply works well for this because it records a profile and opens an interactive view in the browser:
samply record -- cargo run --release
For an independent example workspace, run it from that directory or pass the manifest path:
samply record -- cargo run --release --manifest-path=perf/null/Cargo.toml -- --config=flow
Enable debug symbols for release builds so the profile contains useful function names and source locations:
[profile.release]
debug = true
Add this to the Cargo.toml of the workspace you are profiling. For the root crate, that is the repository root. For a benchmark under perf/, it is usually the Cargo.toml inside that benchmark directory.
The flame graph view is often the most useful starting point. Look for unexpectedly large functions, allocation-heavy paths, synchronization overhead, and time spent outside the block code when the goal is to tune scheduling or buffering.
Stable Measurements
For reproducible results, reduce system noise. One practical approach on a systemd-based Linux machine is to move normal system work onto a small CPU set and run the benchmark on the remaining CPUs.
First, restrict the normal system slices to the CPUs reserved for the operating system:
SYSTEM_CPUS=0,1
sudo systemctl set-property --runtime -- user.slice AllowedCPUs=${SYSTEM_CPUS}
sudo systemctl set-property --runtime -- system.slice AllowedCPUs=${SYSTEM_CPUS}
sudo systemctl set-property --runtime -- init.scope AllowedCPUs=${SYSTEM_CPUS}
Then start the benchmark in its own transient unit on the CPUs reserved for the measurement:
sudo systemd-run --uid=$(id -u) --slice=sdr --wait -P -p AllowedCPUs=2,3 -d -- cargo run --release
The perf/migrate-processes.sh and perf/revert-processes.sh scripts show the same pattern for the repository benchmarks. The settings are runtime-only, but reset them after a measurement run or reboot before using the machine normally.