Skip to content

APU DSP Performance Investigations

OpenAI Codex edited this page Nov 1, 2026 · 2 revisions

APU / DSP Performance Investigations

Reviewed: November 1, 2026 · Source: b7140848
Release availability: Delivered mechanisms span v0.8.136-0-g458730bf5373 (September 30, 2026) and later main; see the matrix below.

Scope: Current delivery, measured research and remaining investigations. Source presence, release availability and demonstrated benefit are separate facts.

The strongest non-graphics performance questions in the reviewed fork are concentrated in the MCPX APU pipeline. The recurring pattern is not a vague “southbridge bottleneck.” It is a chain of small 32-sample audio blocks crossing worker scheduling, guest-memory translation, backend dispatch, DSP memory, DMA conversion, and a long-lived APU mutex.

This page is the entry point. Every formal item has its own technical reference. Two source findings extend existing subject pages rather than creating duplicates.

Current mechanism map

Mechanism Current implementation / release Evidence boundary Full reference
DSP JIT default #183 Merged; in September 30 stable Existing engine now selected by default; broader parity remains; real-time processing still defaults off DSP JIT
Mono sinc #182 Merged; in stable One interpolation channel then output expansion; historical results are not November-head proof Mono resampling
Decoded ADPCM reuse #187 Merged; main only Fresh full-byte comparison; no repeatable native game speedup established; some PGR2 tails worsened ADPCM cache
SGE / ADPCM payload mappings #190/#275 Merged; main only Live descriptor/payload reads; adverse Deck Morrowind outcome retained in #310 Sample mappings and span research
CFG_FMT snapshot #191 Merged November 1; main only Local to one sample-fetch callback; persistent controls/coefficient caches remain research Prepared voice controls
Auto-four workers #200 Merged; main only Auto clamps host logical count to 1–4; explicit 1–16, restart required; independent of linear resampling Worker policy and research
Worker mutex / completion #201/#305 Merged; #201 in stable, #305 main only Reliability fixes; no fabricated speedup percentage Worker reliability
Attenuation table #193 Measured, closed/unmerged +0.70% cadence, −0.62% CPU, worse p99 +6.32%; retained research Attenuation lookup
Linear resampler #189 Open/unmerged/HOLD Separate quality tradeoff; normal current path remains sinc Linear research
Bulk VP → DSP transfer Unimplemented research Existing 32 × 32 handoff uses generic DSP writes; quantify route cost before changing it Bulk transfer
DSP DMA block transfers Unimplemented research Preserve descriptor, memory and completion semantics DMA research
Frame-lock contention / HRTF Remaining research Need runtime contention and cost evidence Frame lock · HRTF
Lazy GP/EP execution Destructive research Skipping processor execution can change guest observations Lazy DSP
Secondary-device counters Instrumentation backlog No speedup inferred from source inspection Opportunity counters

The matrix is a delivery map, not a pooled performance result. Current Status, Releases and Main, and Measured Results keep product availability and timed campaigns explicit.

Operational diagnostics and held GP work

#292 delivers a read-only Linux GDB ownership inspector for an already attached, paused xemu process with matching DWARF. It reports ownership/state observations; it does not itself prove PCM output or audibility. #302 validates offline Vulkan finish attribution and is a diagnostic capability, not a runtime speedup.

The ownership inspector is scripts/xemu-audio-state.py. Load it through GDB's source command using the maintained runner recipe. It reads the selected live inferior and /proc/<pid>/exe, validates required audio DWARF fields, and creates a new audio-state.json plus an XEMU_AUDIO_STATE console record. Output includes executable SHA-256, backend/driver identity, timer/VM flags and hardware/software voice activity. It makes no inferior calls, writes, breakpoints or PCM changes; attach/pause and matching symbols are prerequisites, not actions supplied by the script. An instantaneous ownership snapshot provides no playback-duration, acoustic or performance qualification.

For offline finish analysis, vk-perf-summary.py --finish consumes a complete stopped schema-8 log and retains its source hash and timing coverage. The current contract distinguishes calls, submissions, waits and sampled host elapsed durations. Those durations are not CPU-exclusive or GPU time and are not extrapolated into a speedup. Both diagnostics are main-only additions relative to the September 30 stable release.

GP trace/replay/HLE #376–#380 remains open/unmerged/HOLD. Its reported 94.673% GP share refers to summed GP/EP host elapsed cost in one capture, not total emulator CPU. Fewer replay FFI calls in recognized routines do not establish native FPS improvement, full replay fidelity or a delivered HLE engine. Branch-only diagnostic variables are not current-main settings.

Source-backed priority

flowchart TD
    A[Existing APU frame] --> B[VP voice processing]
    B --> C[Worker wake / clear / process / reduce]
    B --> D[Voice-state and SGE/sample memory access]
    C --> E[32 x 32 VP mix matrix]
    D --> E
    E --> F[1,024 generic writes into GP X memory]
    F --> G[GP DSP interpreter or JIT]
    G --> H[DSP DMA / EP DSP / monitor]
    I[vCPU APU MMIO] --> J[APU main mutex]
    J --> A
Loading

The priority rule is:

frequency × cost × realistically removable fraction × affected workload

A source loop that can theoretically execute 1.5 million backend writes per second deserves counters before a rare timer conversion. A function merely appearing “clever” does not justify a project.

Classification rules

Host optimization

The candidate preserves the current guest-visible result and changes host organization only. Examples:

  • bulk VP-to-DSP writes with identical 24-bit conversion and layout;
  • safe SGE spans that split at every required boundary;
  • queryable worker-count or serial-path selection;
  • mirrored HRTF history and exact SIMD kernels;
  • shorter mutex ownership after immutable frame inputs are proven.

Alternate engine qualification

The DSP interpreter and JIT already exist, and JIT-default selection has landed. Remaining qualification concerns output, state transfer, lifecycle and host/title compatibility, not building another engine or treating the delivered default as future work.

Approximation or destructive experiment

The experiment intentionally changes timing, numerical output, or guest-observable processor execution to estimate an upper bound. Examples:

  • skipping GP/EP while attempting to detect the first later observation;
  • treating a mid-block guest voice record as stable without an ownership proof;
  • “close enough” HRTF parameter convergence;
  • immediate DSP DMA completion where the guest currently observes RUNNING for several reads.

These routes must never be described as normal optimizations until their behavioral contract is established.

Instrumentation-only backlog

A source pattern exists, but call frequency or total cost is unknown. OHCI descriptor reads, PTIMER conversion, NVNet descriptor reuse, IDE copies, and inactive APU wakeups start here.

Prior evidence that prevents a cold start

The reviewed code already establishes several facts:

  • dsp_init() selects either dsp_jit_init() or dsp_c_init() from g_config.audio.use_dsp_jit.
  • dsp_set_engine() synchronizes through the canonical VM state and supports runtime engine changes.
  • The JIT owns X, Y, and program-memory buffers and aliases the DSP mix buffer into X memory.
  • se_frame() creates one 32 × 32 mix matrix, runs VP, then DSP, then monitor processing while the frame thread owns the APU lock.
  • Voice workers clear complete mix matrices and reduce complete matrices into a central result.
  • The VP → GP handoff invokes dsp_write_memory() once per mixbin/sample entry.
  • DSP DMA decodes descriptors, performs word-level backend accesses, and uses an explicitly marked intermediate scratch buffer.
  • HRTF uses 31 taps, two channels, a circular history buffer, fractional delay interpolation, and per-sample coefficient smoothing.

Those facts should be treated as the starting model. New investigations should refine them with runtime counts rather than rediscovering the same call graph.

Remaining investigations, phase 1: opportunity counters

Counters must be low overhead, disabled by default, and owned by the thread/subsystem producing them.

APU subframes
active voices by format and 2D/3D/MP class
HRTF-active voices
worker dispatches and queue lengths
workers awakened / productive / empty
worker clear, processing, wait, and reduction time
physical voice-memory reads
SGE entry reads and resolved spans
VP→DSP generic write calls and words
DSP DMA descriptors, words, backend calls, and scratch copies
GP/EP requested and executed cycles
APU mutex wait and hold time by caller/phase
idle timed wakeups and BQL activity
audio underruns and APU frame deadline misses

For new optimization work, establish route reachability before claiming that a source loop is the bottleneck. Counters listed here are proposed evidence, not a promise that every field exists in current main.

Remaining investigations, phase 2: focused correctness comparison

Reference and candidate runs must start from independent, equivalent state. Compare:

audio samples or a declared numerical/error metric
DSP registers, stack, X/Y/P memory and mix buffer
DMA descriptor/cursor/control state
interrupt and notifier sequence
voice position, envelope and loop state
snapshot result
reset, pause/resume and engine-switch behavior

Include negative controls that violate the optimization’s assumptions. A span test must cross a page/SGE/loop boundary. A cached voice-control test must modify a guest record during the block. A DSP bulk-write test must run a program that reads the mix-buffer alias. A lazy-DSP test must provoke a memory/status/DMA/interrupt observation.

Remaining investigations, phase 3: representative performance campaign

Release build with matching symbols for attribution
A–B–B–A and B–A–A–B where state allows it
same executable where possible
same save state, input and guest-progress window
worker counts 1 / 2 / 4 / 8 / 16 / Auto
DSP interpreter and JIT
HRTF enabled and disabled
x86-64 and ARM64 where backend-specific behavior matters

Record whole-emulator outcomes:

  • vCPU progress;
  • APU frame utilization;
  • CPU time per thread;
  • p95/p99 frame and audio-block time;
  • audio underruns;
  • mutex contention;
  • output divergence;
  • memory use.

Local timers are explanatory evidence, not the acceptance result.

Work deliberately not promoted

Idea Disposition Reason
Generic PCI IRQ deduplication Remove The generic path already exits when INTx level does not change
“Optimize the northbridge” Remove No substantial cycle-level northbridge transaction engine exists in this model
APU idle wakeup as a standalone project Subtask Measure under the lock-contention/instrumentation page first
OHCI descriptor batching Instrument Ownership and guest-RAM completion semantics are nontrivial; runtime cost unknown
PTIMER conversion Instrument Frequency and guest-PC ownership unknown; current truncation must be preserved
NVNet descriptor reuse Conditional backlog Relevant only under network traffic and needs frequency evidence
IDE/ATAPI direct transfer Do not add yet Current path already uses SG/chunk transfers; storage/block latency may dominate

Promotion policy

flowchart LR
    A[Source-backed question] --> B[Performance - Unproven]
    B --> C[Route counters + correctness comparison]
    C -->|Useful evidence, not in main| D[Performance - Researched]
    C -->|No opportunity or failed idea| E[Record result in Researched / lesson]
    D -->|Lands in main| F[Performance - Implemented]
Loading

A failed experiment with real evidence does not return to Unproven. It becomes Researched so the same mistake is not repeated. Implemented records must separately state release availability, defaults, validation limits and adverse results; merging alone is not performance proof.

Related

Clone this wiki locally