Repository navigation
APU Frame Lock Contention
Lifecycle: Performance - Unproven · Type: Measurement-first synchronization investigation
Reviewed source:322986f4c5502b3fa887a9605ac8cd88e9b2c579
Question: Does the APU frame thread hold its main mutex long enough to block vCPU MMIO or control activity materially?
APU/DSP investigation directory · Voice-worker dispatch
mcpx_apu_frame_thread() acquires d->lock before entering its main loop. While holding the mutex, active operation can call throttle() and se_frame(). se_frame() performs VP processing, DSP processing, and monitor/audio work. The thread temporarily drops the mutex only around BQL-protected interrupt update.
GP/EP writes and voice-control operations also acquire the same APU mutex. A plausible interference chain is therefore:
APU thread owns d->lock
→ throttle or wait for audio queue
→ process voices / wait for workers
→ reduce mixbins
→ write/run DSP
→ produce monitor output
vCPU or control path
→ attempts APU MMIO/GP/EP write
→ waits for d->lock
A CPU sampling profile may show the vCPU as waiting rather than attribute time to the APU phase that owns the lock. The first task is instrumentation, not lock splitting.
-
hw/xbox/mcpx/apu/apu.cmcpx_apu_frame_thread()throttle()se_frame()- idle timed wait and BQL interrupt update
- pause/reset/snapshot quiescence
-
hw/xbox/mcpx/apu/vp/vp.c- voice lock/unlock
- worker wait and voice-state commits
-
hw/xbox/mcpx/apu/dsp/gp_ep.c- GP/EP memory/register writes under
d->lock
- GP/EP memory/register writes under
- APU MMIO and method paths that signal the frame condition or mutate shared control state.
sequenceDiagram
participant V as vCPU / MMIO caller
participant A as APU frame thread
participant W as Voice workers
participant D as GP/EP DSP
A->>A: lock d->lock
A->>A: throttle / timed waits
A->>W: dispatch voices
A->>W: wait for completion
W-->>A: worker matrices
A->>A: reduce / build VP output
A->>D: write mix block and run DSP
A->>A: monitor/audio submission
V->>A: request d->lock for write
Note over V,A: vCPU may wait until complete frame ownership ends
A->>A: unlock d->lock
A-->>V: mutex acquired
The diagram is a possible timing, not proof of material contention. The instrumented wait distributions decide whether the overlap occurs in real titles.
Every important mutex acquisition should record caller and wait duration without formatting/logging in the hot path:
typedef enum ApuLockCaller {
APU_LOCK_FRAME_THREAD,
APU_LOCK_GP_WRITE,
APU_LOCK_EP_WRITE,
APU_LOCK_VOICE_METHOD,
APU_LOCK_RESET,
APU_LOCK_SNAPSHOT,
APU_LOCK_AUDIO_RECONFIG,
} ApuLockCaller;
int64_t wait_start = qemu_clock_get_ns(QEMU_CLOCK_REALTIME);
qemu_mutex_lock(&d->lock);
apu_perf_record_lock_wait(caller,
qemu_clock_get_ns(QEMU_CLOCK_REALTIME) - wait_start);The frame thread also needs phase hold timing:
apu_phase_begin(d, APU_PHASE_THROTTLE);
throttle(d);
apu_phase_end(d, APU_PHASE_THROTTLE);
apu_phase_begin(d, APU_PHASE_VP);
mcpx_apu_vp_frame(d, mixbins);
apu_phase_end(d, APU_PHASE_VP);Suggested phases:
idle/timed wait while lock held
throttle/audio queue wait
voice-list traversal and preparation
worker scheduling/wait
worker reduction
VP→DSP transfer
GP DSP
EP DSP
monitor/audio submission
IRQ update/BQL transition
pause/reset/snapshot quiescence
Record cumulative counters and histograms; do not emit one log line per lock operation.
flowchart TD
A[Lock wait/hold counters] --> B{vCPU/control waits material?}
B -->|No| C[Close synchronization redesign; retain evidence]
B -->|Yes| D[Identify owning phase and caller]
D --> E{Work can execute outside lock with stable inputs?}
E -->|No| F[Optimize owning phase first]
E -->|Yes| G[Prototype shorter ownership or message/snapshot design]
G --> H[Race/lifecycle testing]
H --> I[Release A/B]
If worker or DSP optimization shortens the hold time enough, a lock redesign may become unnecessary. Therefore, contention should be measured before and after other APU work.
Copy the guest/control state needed for one 32-sample block while holding the mutex, then process the immutable snapshot outside it. Commit state changes under a shorter ownership window.
qemu_mutex_lock(&d->lock);
ApuFrameInputs in = capture_frame_inputs(d);
qemu_mutex_unlock(&d->lock);
ApuFrameResult out = process_frame(&in);
qemu_mutex_lock(&d->lock);
commit_frame_result(d, &in, &out);
qemu_mutex_unlock(&d->lock);The hard part is guest writes that occur between capture and commit. The design needs generations/conflict detection or a defined block-boundary model.
MMIO/control callers enqueue small ordered mutations without waiting for the complete frame. The APU thread applies them at safe boundaries.
This can change when a guest write becomes observable and must be treated as a timing-model change unless the hardware already latches at equivalent boundaries.
Separate rarely contended control/register structures from long-lived processing state. This requires a complete shared-state inventory; splitting one mutex into two without ordering rules can create deadlocks or inconsistent snapshots.
Use immutable/versioned configuration snapshots for fields read frequently but changed rarely. Writers publish a new version; the frame retains the old version until completion.
Worker dispatch, HRTF, VP→DSP transfer, and DSP execution may be optimized without changing synchronization. This is the lowest-risk response when one phase dominates hold time.
When the APU is inactive, the frame thread performs a short timed wait while holding/reacquiring its mutex and may drop it to acquire the BQL for interrupt update.
Keep this as a sub-investigation:
inactive timed wakeups
wakeups that observe no state change
BQL acquisitions
BQL wait/hold time
IRQ update calls
actual IRQ level transitions
Do not create an independent idle-wakeup optimization unless these counts are significant.
- Ordered application of guest MMIO/method writes.
- Voice lock semantics.
- DSP memory/register visibility.
- DMA control and interrupt timing.
- Condition-variable wakeups and no lost wake.
- Pause/reset/snapshot quiescence.
- No lock-order cycle with BQL, worker mutexes, audio/SDL objects, or DSP callbacks.
- Frame state cannot be committed over newer guest writes silently.
- Teardown cannot leave vCPU/control callers waiting forever.
lock acquisitions by caller
wait count / total / max / p50 / p95 / p99 by caller
hold count / total / max by owner phase
vCPU thread waits attributed to APU lock
frame-thread idle wakeups
condition signals/broadcasts
BQL acquisitions, waits and actual IRQ transitions
worker wait time while APU lock is held
GP/EP run time while lock held
audio submission/throttle wait while lock held
APU frame deadline misses
audio underruns
A sampling profiler should be correlated with scoped lock counters. Neither alone is sufficient.
| Scenario | Required property |
|---|---|
| Concurrent voice-register writes | No lost/reordered write |
| GP/EP memory writes during processing | Defined visibility and no corruption |
| Voice lock/unlock | Stable ownership and no deadlock |
| DMA control/status activity | Correct ordering and interrupts |
| Heavy HRTF/voice frame | Hold phases attributed correctly |
| Audio queue backpressure | Throttle wait identified separately |
| Reset and pause/resume | Quiescence completes |
| Snapshot save/load | Canonical state and thread state safe |
| Shutdown | No waiter or worker remains blocked |
| Audio engine reconfiguration | No use-after-free or stale stream state |
| Artificial lock delay | Counters and vCPU attribution detect it |
- Can close the issue cheaply if contention is negligible.
- Identifies the actual owning phase instead of blaming a broad lock.
- Supports prioritization among worker, HRTF, DSP, and audio-output work.
- A successful shorter-ownership model can improve vCPU responsiveness without changing local APU throughput.
- Timing instrumentation can perturb short waits if too detailed.
- Lock splitting is a high-risk race/deadlock project.
- Message queues or frame snapshots may change guest-visible write timing.
- A long hold is not harmful when no other thread needs the lock.
- vCPU waiting may be dominated by another subsystem or scheduler decision.
Move to Researched when low-overhead counters show material vCPU/control wait time and identify repeatable owning phases. Any redesign must then pass race/lifecycle tests and improve whole-emulator progress or tails in Release builds.
If p95/p99 wait time and total blocked vCPU time are negligible, record the negative result and do not split the lock. Optimize only independently hot APU phases.
DSP tooling guide and navigation updated November 2, 2026 for experimental PR #384, source 8f51d237. Capture/replay usage and limits.
The earlier wiki-wide review was published November 1, 2026. Emulator source: b7140848. Reviewed wiki content: 1d2b671. Current status · Release availability · Publication process.
- Home
- Current status
- Stable versus main
- Implemented mechanisms
- Controls and defaults
- Measured results and limits
- Complete change register
- System
- CPU and vCPU
- Memory and DMA
- Graphics / NV2A
- APU and audio
- Timing and timers
- Platform and I/O
- Hardware observations
- Fidelity boundaries
- Methodology
- PGR2 routes
- Debugging lag / hot paths
- Tools and commands
- DSP capture, replay and fuzzing
- HLE readiness and public XISO proof
- Concurrent DSP shadow and OpenAL proof
- DSP HLE operation inventory
- DSP HLE fix examples and testing
- Development
- Upstream shortlist
- Wiki maintenance
- Research / retired work
- Optimization catalog
- Hot-path shortcuts
- Researched experiments
- Unproven proposal directory
- PR lessons
- September26 snapshot
Repository · PRs · Issues