# Open-IOV Performance Testing & A/B Methodology > **Status: superseded (historical).** Part of the pre-2026-09-23 docs; kept > as a record. Current entry point: [docs/README.md](README.md). Performance > replacement: > [05-operations/01-performance-correctness-security.md](05-operations/01-performance-correctness-security.md). > How we measure vGPU performance against bare metal, interpret the results, > and keep overhead on the critical path honest. **Run correctness first** > (`docs/DEBUGGING.md` §5) — numbers from a half-working stack are noise. --- ## 1. What overhead is acceptable (targets) | Path | Target | Why | |------|--------|-----| | GSP RPC round-trip (doorbell→response→MSI-X) | < 50 µs (stretch: < 10 µs) | every driver op goes through here | | MSI-X injection (eventfd → guest handler) | < 5 µs | completion latency dominates | | Guest BAR0 MMIO read/write | < 2 µs (shadow RAM) | hot path for register polling | | CUDA kernel launch (added overhead vs bare metal) | < 10% | user-visible | | Memory alloc/map RPCs | < 100 µs | setup-time, amortized | | CUDA compute throughput | ≥ 95% of bare metal | GPU-bound, should be ~free | | Graphics (frame rate) | ≥ 90% of bare metal | swapchain + submission overhead | ## 2. The A/B rule Every measurement runs **twice on the same machine, same boot if possible**: - **A (bare metal)**: workload on the physical GPU via the host's stock driver (or our OpenRM nvidia.ko directly on the hardware). - **B (vGPU)**: identical workload in the guest on the emulated vGPU. Same binary, same input sizes, same environment (governor, clocks, hugepages). Report **B/A ratio %** — never absolute numbers alone. One variable at a time. If B regresses, you changed the stack, the workload, or the machine — find which before optimizing. ## 3. Microbenchmarks (build these first) 1. **RPC round-trip**: from the guest, submit a DEBUG/NOOP-ish RPC in a loop; time doorbell-write → response-visible. Host side: `gsp_emu_get_stats()` gives `total_latency_ns` and counts — divide for per-RPC host cost. (Add `trace_printk` in `gsp_emu_dispatch_rpc` entry/exit for distribution; read via `/sys/kernel/debug/tracing/trace`.) 2. **MSI-X latency**: timestamp (guest TSC) at RPC submit → in the guest driver's interrupt handler. Compare with the host eventfd_signal timestamp. 3. **BAR0 shadow read latency**: guest loops reading a shadow register; expect ~memory speed (uncached, so ~100-300 ns) — anything ≥ 1 µs means you're trapping somewhere you shouldn't. 4. **Doorbell polling cost**: host `top`/`perf` on `gsp_emu_N` — the adaptive poll (busy → 50-100 µs sleep) should sit at ~0% CPU when idle, and react within one sleep quantum under load. Tune `GSP_EMU_POLL_*` only with data. ## 4. Workload benchmarks Runner: `tools/benchmark_infrastructure.sh` (extend it; keep the A/B pattern). In-guest execution goes through the harness (`vgpu_vm.py exec`) or the guest test agent (`tools/guest_agent/`). | Suite | What | Pass criterion | |-------|------|----------------| | `cuda-samples vectorAdd` | smoke + element-wise throughput | correct result; ≥ 95% B/A | | `simpleCUBLAS` | SGEMM TFLOPS | ≥ 95% B/A | | `nvbandwidth` (if available) | H2D/D2H/device copy GB/s | report; DMA path validation | | `glxgears -info` / a real GL app | frame rate | ≥ 90% B/A | | `nvenc`/`nvdec` sample | transcode fps | functional (perf later) | | PyTorch CUDA op (matmul loop) | end-to-end framework | ≥ 90% B/A | Always log: guest/host driver versions, GPU clocks (`nvidia-smi -q -d CLOCK` both sides — throttling skews everything), and the command lines. ## 5. Where the overhead lives (profile in this order) 1. **RPC dispatch** — the single biggest lever. Everything the guest driver does funnels through the emulation thread. 2. **Doorbell polling cadence** — too slow = latency; too hot = host CPU burn. 3. **eventfd/MSI-X delivery** — measure, don't assume. 4. **BAR0 uncached semantics** — shadow is UC for the guest; that's MMIO-correct but slow for large reads. If the driver bulk-reads shadow, consider caching-safe sub-regions. 5. **Memory model gap** (ALLOC host-pages vs BAR1 direct VRAM) — see `docs/GSP_EMULATION_ARCHITECTURE.md` §3. Until unified, DMA-heavy workloads are not representative. ## 6. Optimizing safely - Change one thing, re-run the A/B pair, keep the numbers with the commit. - Never optimize off bare-metal-only data: the goal is B/A, not A. - If a change makes B faster but breaks the §5 validation checklist in `docs/DEBUGGING.md`, it doesn't count. - Record results in `STATE.md` (date, versions, B/A ratios) so regressions are visible across commits. ## 7. Reporting template ``` Date / commit / versions (guest=host=firmware=emulation): Machine / GPU / clocks locked?: Test, unit, A (bare), B (vGPU), B/A %: Notes (what changed vs last run): ```