2026-11-06 15:20:20 +01:00
|
|
|
|
# Performance and Realtime Qualification
|
2026-10-27 01:21:04 +01:00
|
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
|
The original `test-performance` ran a hardware-independent 48 kHz, 128-frame
|
|
|
|
|
|
stereo workload: 64 graph nodes, 4096 MIDI notes (32 simultaneous), 16 tracks
|
|
|
|
|
|
and gain automation. Preparation and block timing use CLOCK_MONOTONIC outside
|
|
|
|
|
|
the engine; callback code itself does not clock or log. The test checks finite
|
|
|
|
|
|
output and reports mean, p99, maximum and CPU-time/audio-duration ratio. A run
|
|
|
|
|
|
is a workload measurement, not a universal realtime threshold. Run more blocks
|
|
|
|
|
|
with:
|
2026-10-27 01:21:04 +01:00
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
|
./build/test_performance 10000
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
|
Initial measurement on an AMD Ryzen Threadripper 1920X, GCC 16.2.1, -O2, 2000
|
|
|
|
|
|
blocks: preparation 3.045 ms, mean 308.301 us, p99 422.153 us, maximum 665.096
|
|
|
|
|
|
us, versus a 2666.667 us block deadline; CPU/audio ratio 0.1156. These are one
|
|
|
|
|
|
host/run's observed values and depend on scheduling/load/compiler. The test
|
2026-10-27 01:21:04 +01:00
|
|
|
|
excludes WAV disk preparation, external/native plugin complexity, GUI redraw,
|
|
|
|
|
|
server scheduling and actual hardware latency. Native callbacks must provide
|
|
|
|
|
|
their own bounded realtime implementation.
|
|
|
|
|
|
|
|
|
|
|
|
Existing software JACK loopback, stereo MIDI/save/reopen, record/reopen,
|
2026-11-06 15:20:20 +01:00
|
|
|
|
restart/close and concurrent graph handoff tests remain available. Runtime fault
|
|
|
|
|
|
injection covers loss/rate/xrun/null-buffer and lifecycle failures without
|
2026-10-27 01:21:04 +01:00
|
|
|
|
altering the shared server. ASan/UBSan validate memory/error paths; TSan checks
|
2026-11-06 15:20:20 +01:00
|
|
|
|
recorder/handoff races, not timing. Sanitized runs are not latency
|
|
|
|
|
|
qualification.
|
2026-10-27 01:21:04 +01:00
|
|
|
|
|
|
|
|
|
|
Current worst-case storage includes up to 256 MiB preloaded mono WAV samples,
|
2026-11-06 15:20:20 +01:00
|
|
|
|
paired graph buffers (128 MiB at 256 nodes/65536 frames; GUI limit 32 MiB),
|
|
|
|
|
|
bounded MIDI/mix metadata and up to 33 metadata/graph history snapshots.
|
|
|
|
|
|
Pending/retired plans can retain additional graph memory. Native preparation can
|
|
|
|
|
|
allocate more; the API cannot enforce an extension's memory budget. There is no
|
|
|
|
|
|
CPU overload scheduler, disk streaming, latency compensation or callback
|
|
|
|
|
|
allocator audit of arbitrary third-party code. High-density interactive
|
|
|
|
|
|
projects, physical devices, long-duration stereo/recording and live server
|
|
|
|
|
|
termination remain unqualified.
|
2026-10-27 06:35:15 +01:00
|
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## Matrix, Capacity and Contention Checks
|
2026-10-27 06:35:15 +01:00
|
|
|
|
|
2026-11-06 19:05:14 +01:00
|
|
|
|
`cmake --build build --target benchmark` records 24 warmed graph cases: 2/16/64/256 nodes, mono/stereo,
|
2026-11-06 15:20:20 +01:00
|
|
|
|
44.1k/31, 48k/128 and 96k/1024. Each reports control-thread compile time
|
|
|
|
|
|
separately from 300 process calls, mean/p99/max, block deadline, mean load, and
|
|
|
|
|
|
paired graph buffer bytes. Timing, sorting, allocation and logging happen
|
|
|
|
|
|
outside processing. Metered gain chains test graph scaling; they do not
|
|
|
|
|
|
represent expensive plugins. The checked-in `performance-baseline.json` records
|
|
|
|
|
|
CPU/compiler/flags/kernel and all measurements. Copy a new run only after
|
|
|
|
|
|
reviewing changes and conditions.
|
2026-10-27 06:35:15 +01:00
|
|
|
|
|
|
|
|
|
|
```sh
|
2026-11-06 19:05:14 +01:00
|
|
|
|
cmake --build build --target benchmark test-performance test-scale test-stress
|
|
|
|
|
|
cmake -S . -B build -DXTC_BENCHMARK_MAX_REGRESSION=1.5 && cmake --build build --target benchmark-compare
|
2026-10-27 06:35:15 +01:00
|
|
|
|
# Select a baseline from this same host/compiler/flags:
|
2026-11-06 19:05:14 +01:00
|
|
|
|
cmake -S . -B build -DXTC_BENCHMARK_BASELINE=/path/to/performance.json && cmake --build build --target benchmark-compare
|
2026-10-27 06:35:15 +01:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Comparison rejects mismatched CPU/architecture/compiler/flags or missing cases.
|
|
|
|
|
|
It fails if mean time exceeds both the ratio budget and a 1 microsecond absolute
|
|
|
|
|
|
noise allowance. p99/max are reported for investigation, not universally gated.
|
|
|
|
|
|
Repeat surprising results under comparable load; do not silently replace the
|
|
|
|
|
|
baseline to make a regression disappear. Scheduling, clocks, thermal state and
|
|
|
|
|
|
shared-machine contention still affect results. No real-time scheduling/pinning
|
|
|
|
|
|
or external profiler is required. CPU-pressure runs are correctness checks, not
|
2026-11-06 19:05:14 +01:00
|
|
|
|
comparable timing baselines. Tests/benchmark outputs live below the selected binary directory.
|
2026-10-27 06:35:15 +01:00
|
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
|
Initial matrix (Threadripper 1920X, GCC 16.2.1, -O2, 300 blocks): 256-node
|
|
|
|
|
|
stereo 48k/128 mean 353.214 us, p99 396.495 us, load .132; 96k/1024 mean
|
|
|
|
|
|
2478.520 us, p99 3092.434 us, load .232. The full JSON is the evidence. These
|
|
|
|
|
|
met the local investigation target of mean load <.5 for ordinary graph chains;
|
|
|
|
|
|
this is a host-specific development target, not a supported maximum CPU or
|
|
|
|
|
|
latency promise.
|
2026-10-27 06:35:15 +01:00
|
|
|
|
|
|
|
|
|
|
The mixed 64-node/4096-note workload measured 340–357 us before removing unused
|
|
|
|
|
|
live-controller processing and 313 us after (mean over 2000 blocks); p99 varied
|
2026-11-06 15:20:20 +01:00
|
|
|
|
with system load. The measured hot-path change skips live voices/controller
|
|
|
|
|
|
ramps when neither is active and avoids finished-ramp calls. Exact CC/odd-block
|
|
|
|
|
|
tests continue to pass. No other graph optimization was justified by this
|
|
|
|
|
|
measurement.
|
2026-10-27 06:35:15 +01:00
|
|
|
|
|
|
|
|
|
|
`test-scale` constructs all 64 tracks, 1024 clips, 1024 automation points and
|
2026-11-06 15:20:20 +01:00
|
|
|
|
4096 notes, compiles a standalone 256-node metered graph, renders stereo
|
|
|
|
|
|
odd-sized blocks and rejects capacity excesses. Project/GUI graphs remain capped
|
|
|
|
|
|
at 64 nodes. `test-stress` runs publication, allocation/retirement, capacity and
|
|
|
|
|
|
optional GUI/timer/redraw tests while two ordinary CPU workers compete;
|
|
|
|
|
|
subprocesses have timeouts and workers are always terminated. Existing callback
|
|
|
|
|
|
guards verify no host allocation/free/loading/unloading. This does not qualify
|
|
|
|
|
|
arbitrary plugins.
|
2026-10-27 06:35:15 +01:00
|
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
|
Callback work is bounded by documented node/frame/clip/note/event/point
|
|
|
|
|
|
capacities. Exceeding model/plan/mailbox capacities returns CAPACITY
|
|
|
|
|
|
transactionally; invalid or overlarge processing buffers are silenced and
|
|
|
|
|
|
reported, JACK xruns are counted. There is no CPU scheduler or preemption inside
|
|
|
|
|
|
a DSP callback: an expensive trusted plugin can miss a deadline or hang.
|
|
|
|
|
|
Automated LADSPA lanes currently use run(1) and bounded per-sample lane searches
|
|
|
|
|
|
and may be costly; they are excluded from the gain-chain baseline. Large WAV
|
|
|
|
|
|
memory limits remain those in AUDIO_FILES.md. Physical-device endurance,
|
|
|
|
|
|
CPU-overload sound quality and a full GUI latency benchmark remain unqualified.
|
2026-10-27 17:07:37 +01:00
|
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## Qualification Workloads
|
2026-10-27 17:07:37 +01:00
|
|
|
|
|
|
|
|
|
|
The current `test-performance` emits one JSON record (the old prose output is
|
|
|
|
|
|
retired). Default: 2,000 stereo blocks at 48 kHz/128, 64 graph nodes, 16 tracks,
|
|
|
|
|
|
1,024 stored notes and 256 automation points, at most 32 active voices. This
|
2026-11-06 14:12:22 +01:00
|
|
|
|
changes the original workload; its historical numbers above are not a baseline
|
2026-10-27 17:07:37 +01:00
|
|
|
|
for this revision. `benchmark-project` runs nine cases: 1/16/64 tracks,
|
|
|
|
|
|
64/1,024/4,096 stored notes, 16/256/1,024 points and 31/128/1,024-frame buffers.
|
|
|
|
|
|
Two long notes per first 16 tracks remain audible. Other notes are scheduled
|
|
|
|
|
|
later to remain within the 32-voice plan limit. The position wraps every 96,000
|
|
|
|
|
|
samples to keep long runs active. Future notes exercise storage/preparation and
|
2026-11-06 15:20:20 +01:00
|
|
|
|
bounded scans rather than additional simultaneous synthesis. Each track has 16
|
|
|
|
|
|
gain points; graph processing uses the same 64-node chain in every case. The
|
|
|
|
|
|
graph matrix separately scales nodes and chain connections from 2 to 256. These
|
|
|
|
|
|
synthetic cases do not qualify every possible dense graph or plugin chain.
|
2026-10-27 17:07:37 +01:00
|
|
|
|
|
|
|
|
|
|
```sh
|
2026-11-06 19:05:14 +01:00
|
|
|
|
cmake --build build --target benchmark benchmark-project
|
|
|
|
|
|
cmake --build build --target benchmark-compare
|
|
|
|
|
|
cmake --build build --target benchmark-project-compare
|
2026-10-27 17:07:37 +01:00
|
|
|
|
# Baselines must match the current CPU/compiler/flags and workload.
|
2026-11-06 19:05:14 +01:00
|
|
|
|
cmake -S . -B build -DXTC_PROJECT_BENCHMARK_BASELINE=/path/to/report.json && cmake --build build --target benchmark-project-compare
|
2026-10-27 17:07:37 +01:00
|
|
|
|
./build/test_performance 10000 64 128
|
2026-11-06 19:05:14 +01:00
|
|
|
|
cmake --build build --target test-benchmark-tools
|
2026-10-27 17:07:37 +01:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Both matrices report wall-clock mean/p99/max processing duration and the count
|
|
|
|
|
|
of measured calls exceeding frames/sample-rate. These are offline deadline
|
|
|
|
|
|
exceedances, not actual JACK xruns: there is no server scheduling in the timing
|
|
|
|
|
|
loop. Graph compilation or project construction/preparation is timed separately.
|
|
|
|
|
|
`process_cpu_ms` measures CLOCK_PROCESS_CPUTIME_ID over the complete measured
|
|
|
|
|
|
loop, including timing and finite-output checks; it excludes preparation and
|
2026-11-06 15:20:20 +01:00
|
|
|
|
sorting. Project `cpu_audio_ratio` divides that CPU time by simulated audio
|
|
|
|
|
|
time. `peak_rss_kib` is Linux getrusage process high-water RSS, including model,
|
|
|
|
|
|
plans, measurement storage and executable/runtime overhead; it is not current
|
|
|
|
|
|
heap size or a leak test. Graph buffer bytes remain a separate exact buffer-size
|
|
|
|
|
|
estimate. Timing probes, memory queries, sorting and output occur outside engine
|
|
|
|
|
|
calls.
|
2026-10-27 17:07:37 +01:00
|
|
|
|
|
|
|
|
|
|
Checked-in `project-performance-baseline.json` provides initial project cases;
|
2026-11-06 14:12:22 +01:00
|
|
|
|
the original graph baseline is retained. `graph-performance-baseline.json` adds
|
2026-11-06 15:20:20 +01:00
|
|
|
|
a current graph report with CPU/RSS/deadline fields. Gates use the existing 1.5x
|
|
|
|
|
|
mean-time budget and 1 us noise allowance, with p99/max/misses and memory
|
2026-10-27 17:07:37 +01:00
|
|
|
|
reported for investigation. There is no universal hardware-independent deadline
|
|
|
|
|
|
or RSS pass threshold. `test-benchmark-tools` verifies same-context success,
|
|
|
|
|
|
regression rejection, missing cases and host/workload mismatch without relying
|
|
|
|
|
|
on machine timing. Do not replace a failing baseline without investigating.
|
|
|
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## Endurance, Faults and Callback Audit
|
2026-10-27 17:07:37 +01:00
|
|
|
|
|
|
|
|
|
|
```sh
|
2026-11-06 19:05:14 +01:00
|
|
|
|
cmake --build build --target test-soak test-stress test-stress-plugins
|
|
|
|
|
|
cmake -S . -B build -DXTC_STRESS_ROUNDS=10 && cmake --build build --target test-stress
|
2026-10-27 17:07:37 +01:00
|
|
|
|
./build/test_soak 10000000
|
2026-11-06 19:05:14 +01:00
|
|
|
|
cmake --build build --target check-development
|
2026-10-27 17:07:37 +01:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
`test-soak` renders exactly 1,000,000 alternating 31/128/1,024-frame stereo
|
|
|
|
|
|
blocks: 2.282 hours of virtual 48 kHz audio. It checks every output sample,
|
|
|
|
|
|
processed block counts and final publication acknowledgement, with 9,999
|
|
|
|
|
|
quiescent position/rebuild cycles. It runs faster than wall time and models
|
|
|
|
|
|
position wrap/seek and restart publication; it is not a real device start/stop
|
|
|
|
|
|
or hardware endurance test. The project benchmark separately wraps active MIDI.
|
|
|
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
|
`test-stress` defaults to three rounds under two ordinary CPU workers. Each
|
|
|
|
|
|
round runs concurrent publication, allocation/lifetime guards, maximum model
|
|
|
|
|
|
capacity, project relocation/reload and missing-media retention, save-fault
|
|
|
|
|
|
diagnostics, and the soak test. With JACK headers present it also repeats the
|
|
|
|
|
|
deterministic backend fake's start/stop/seek, rate/buffer/server-loss and
|
|
|
|
|
|
failure/retry cases. Optional GUI tests run after those rounds under the same
|
|
|
|
|
|
CPU load. `test-stress-plugins` adds the original LADSPA fixture's
|
|
|
|
|
|
identity/layout/range, instantiation-error and invalid-metadata cases each
|
|
|
|
|
|
round; it needs LADSPA headers. No command changes a physical device, kills the
|
|
|
|
|
|
shared server or loads unknown plugins. Subprocess deadlines bound each test and
|
|
|
|
|
|
workers are cleaned up on exit.
|
2026-10-27 17:07:37 +01:00
|
|
|
|
|
|
|
|
|
|
The reliability executable now interposes fopen/fread/fwrite/read/write,
|
2026-11-06 15:20:20 +01:00
|
|
|
|
pthread_mutex_lock and pthread_cond_wait in addition to
|
|
|
|
|
|
malloc/calloc/realloc/free. Guarded host graph/realtime render calls fail
|
|
|
|
|
|
immediately on these operations. This complements the existing native lifetime
|
|
|
|
|
|
and plugin callback guards and control/callback ownership review in
|
|
|
|
|
|
RELIABILITY.md. It does not intercept every libc entry point, raw syscalls,
|
|
|
|
|
|
hidden dynamic-module calls, or GUI code by magic; third-party DSP remains
|
|
|
|
|
|
trusted. No known host callback safety violation was found.
|
2026-10-27 17:07:37 +01:00
|
|
|
|
|
|
|
|
|
|
`check-development` combines the ordinary build/tests, capacity/soak checks,
|
|
|
|
|
|
benchmark gate tests, static analysis and ASan/UBSan. The sanitizer target now
|
|
|
|
|
|
includes maximum capacity and virtual endurance. These are opt-in developer
|
|
|
|
|
|
checks requiring installed Clang/Python; no runtime dependency was added.
|
|
|
|
|
|
Sanitized/contended/profiled runs must not become timing baselines.
|
|
|
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## Profiling Evidence and Remaining Costs
|
2026-10-27 17:07:37 +01:00
|
|
|
|
|
2026-11-06 14:12:22 +01:00
|
|
|
|
Before considering DSP changes, a 10,000-block, 64-track/128-frame project was
|
|
|
|
|
|
sampled with GCC `-O2 -g -pg` and GNU gprof on the documented baseline host.
|
2026-10-27 17:07:37 +01:00
|
|
|
|
Flat samples attributed about 30% to mixer automation `value`, 29% to MIDI
|
|
|
|
|
|
`segment`, and 11% to finite-value handling. Instrumentation and inlining affect
|
2026-11-06 15:20:20 +01:00
|
|
|
|
attribution; this identifies investigation candidates, not exact production cost
|
|
|
|
|
|
or evidence that checks should be removed. MIDI scans bounded stored-note arrays
|
|
|
|
|
|
and evaluates per-voice, per-sample gain/pan lanes. No DSP optimization was
|
|
|
|
|
|
introduced without a separate correctness/performance justification.
|
2026-10-27 17:07:37 +01:00
|
|
|
|
|
|
|
|
|
|
Reproduce in an isolated build (GNU gprof must already be installed):
|
|
|
|
|
|
|
|
|
|
|
|
```sh
|
2026-11-06 19:05:14 +01:00
|
|
|
|
cmake -S . -B build/profile -DCMAKE_C_FLAGS='-O2 -g -pg -std=c99 -Iinclude' -DCMAKE_EXE_LINKER_FLAGS=-pg && cmake --build build/profile --target test_performance
|
2026-10-27 17:07:37 +01:00
|
|
|
|
GMON_OUT_PREFIX="$PWD/build/profile/gmon" ./build/profile/test_performance 10000 64 128
|
|
|
|
|
|
gprof build/profile/test_performance build/profile/gmon.* > build/profile/profile.txt
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Remaining limits include per-sample LADSPA automation run(1), stored-note/clip
|
2026-11-06 14:12:22 +01:00
|
|
|
|
scans, preloaded WAV memory and immutable-plan/history overlap. This work does
|
2026-10-27 17:07:37 +01:00
|
|
|
|
not qualify physical-device changes, arbitrary plugin hangs/crashes, multi-hour
|
|
|
|
|
|
wall-clock sessions or GUI redraw latency. Virtual endurance and deterministic
|
|
|
|
|
|
faults provide repeatable software evidence, not a hard realtime guarantee.
|
|
|
|
|
|
|
2026-11-06 14:12:22 +01:00
|
|
|
|
Observed results (same Threadripper/GCC host, 300 blocks per case):
|
2026-10-27 17:07:37 +01:00
|
|
|
|
64-track/128-frame project mean 380.417 us, p99 720.271 us, prepare 11.841 ms,
|
|
|
|
|
|
CPU/audio .142 and peak process RSS 19,296 KiB. The 256-node stereo/128 graph
|
|
|
|
|
|
mean was 286.398 us, p99 484.712 us, compile .300 ms. These case runs observed
|
|
|
|
|
|
zero deadline exceedances. See JSON for all cases and host metadata.
|
|
|
|
|
|
|
2026-11-06 15:20:20 +01:00
|
|
|
|
The project comparison passed. Repeated graph comparisons flagged variable small
|
|
|
|
|
|
cases despite unchanged graph processing code; a 3,000-block run against the
|
|
|
|
|
|
retained baseline still flagged 16-node mono/31-frame mean 5.089 us above its
|
|
|
|
|
|
1.5x budget. The new graph report is evidence, not a replacement chosen to make
|
|
|
|
|
|
that comparison pass. This shared-host timing alert remains an investigation
|
|
|
|
|
|
item; no CPU/latency guarantee is inferred from the correctness suite. Longer
|
|
|
|
|
|
comparisons can use `BENCHMARK_BLOCKS=3000`.
|