Repository navigation
Hot Path Windows QPC Conversion
Lifecycle: Performance - Implemented · Type: Exact arithmetic shortcut
State: Present in currentmain.
| Field | State at reviewed main
|
|---|---|
| Underlying optimization | Implemented |
| Current control | None; route is selected automatically at process startup |
| Planned control | Issue #99: developer-only Auto / Generic reference
|
| Planned apply boundary | Restart/startup only |
| Maintained reference | muldiv64(ticks, 1,000,000,000, frequency) |
| Evidence counters | Planned low-frequency route/profile evidence; absent from reviewed main |
| Qualification | Source arithmetic path live; same-binary policy comparison not implemented |
| Reviewed source |
322986f4c5502b3fa887a9605ac8cd88e9b2c579, 2026-09-14 |
| Primary history | Windows clock optimization associated with issue #19; planned diagnostic #99 |
On Windows, xemu/QEMU reads QueryPerformanceCounter and converts counter ticks to nanoseconds. The generic conversion uses muldiv64, which preserves precision for arbitrary QPC frequencies but can compile into expensive wide multiply/divide work on an extremely hot path.
At startup, current main queries QueryPerformanceFrequency. If one second in nanoseconds is exactly divisible by that frequency, xemu stores an exact integer nanoseconds-per-tick scale. Every later get_clock() call performs one branch:
exact scale available -> ticks * scale
otherwise -> muldiv64 reference
Issue #99 plans a restart-latched developer diagnostic that can force the maintained generic route even on exact-capable hosts. There is deliberately no “Force exact” mode for non-divisible frequencies.
get_clock() participates in timers, polling, profiling, deadlines, main-loop work, and many device/renderer paths. A small per-call saving can matter because call frequency can be very high.
Potential saving:
clock reads on exact-capable host
× avoided generic muldiv64 / wide division cost
It helps only when the host QPC frequency admits a lossless integer scale and clock conversion is materially hot. It does not reduce the cost of QueryPerformanceCounter itself, callers' timer logic, or unrelated virtual clock computation.
The optimized branch is present in reviewed main. On common Windows systems with a 10-MHz QPC frequency, one tick maps exactly to 100 ns; the multiply and generic conversion are mathematically equal for the supported range.
The reviewed binary does not expose the proposed generic-reference control, requested/effective status, or route evidence. Therefore a same-executable Auto/Generic retail campaign cannot yet be performed through the planned contract. Source and focused arithmetic checks can establish correctness, but current end-to-end benefit remains host/workload-dependent.
Direct host timing guidance: Microsoft recommends QPC for high-resolution interval timing, states that QueryPerformanceFrequency is fixed at boot, and discusses converting counter values without losing precision: Acquiring high-resolution time stamps.
Direct host API: QueryPerformanceFrequency returns the fixed counts-per-second frequency used to interpret QPC values: QueryPerformanceFrequency.
Microsoft's guidance does not prescribe xemu's exact-multiply specialization. xemu's optimization is valid because it retains the generic path unless exact divisibility is proven.
Host monotonic counter ticks
↓
xemu/QEMU host-time conversion
exact multiply OR generic muldiv64
↓
host nanoseconds used by emulator clocks/waits/profiling
↓
emulator scheduling and timing decisions
This is not the emulated Xbox CPU clock, PIT, RTC, or NV2A PTIMER. It is a host utility conversion that influences when emulator components believe host time has advanced.
| Location | Role | Owner |
|---|---|---|
util/qemu-timer-common.c::init_get_clock() |
Query QPC frequency once, derive exact scale, initialize clock_start
|
Process constructor/startup |
include/qemu/timer.h::get_clock() |
Hot inline QPC read and multiply/reference branch | Any calling thread |
clock_freq |
Immutable startup frequency | Process-global read-only after init |
clock_ns_per_tick |
Current active exact scale; zero selects generic | Startup-written, hot-read |
| issue #99 planned policy | Separates immutable capability from active startup route | Not implemented at reviewed main |
tests/unit/test-xemu-windows-qpc.py and planned arithmetic C tests |
Source/selection/equality coverage | Test process |
flowchart TD
subgraph Startup
A[QueryPerformanceFrequency] --> B{Call succeeds and frequency valid?}
B -->|No| C[Fatal clock calibration failure]
B -->|Yes| D{1,000,000,000 mod frequency == 0?}
D -->|Yes| E[clock_ns_per_tick = exact quotient]
D -->|No| F[clock_ns_per_tick = 0]
E --> G[clock_start = get_clock]
F --> G
end
subgraph EveryClockRead
H[QueryPerformanceCounter] --> I{clock_ns_per_tick nonzero?}
I -->|Yes| J[Return ticks * exact scale]
I -->|No| K[Return muldiv64 ticks, 1e9, frequency]
end
The hot path contains no general settings lookup, mutex, allocation, logging, or additional policy object.
Planned Generic reference mode should select at startup:
flowchart TD
A[QPC frequency and exact capability discovered] --> B[Requested startup policy = Generic reference]
B --> C[Active clock_ns_per_tick = 0]
C --> D[Every get_clock reads QPC]
D --> E[muldiv64 conversion]
E --> F[Same nanosecond result on exact-capable host]
For a non-divisible frequency, Auto already uses this route. Generic reference changes nothing and should report that clearly.
| Invariant | Why required | Failure if weakened |
|---|---|---|
| Frequency > 0 and query succeeds | Conversion divisor/source must be valid | Divide by zero/invalid time |
Exact route only when 1e9 % freq == 0
|
Integer scale must be lossless | Systematic drift/truncation |
Generic muldiv64 retained |
Arbitrary frequencies need precise ratio | Wrong nanoseconds |
| Scale/frequency immutable after startup | Hot callers are unsynchronized | Data race/time discontinuity |
No live policy lookup in get_clock()
|
Extra atomic/config read contaminates optimization | Reduced benefit/new contention |
| Multiply stays in supported integer range | Result must not wrap intended clock domain | Time reversal/large jump |
| Both routes monotonic | Callers assume forward host time | Timer/order failure |
clock_start coherent with selected initial route |
Relative clock origin must match conversion | Offset discontinuity |
| Tracepoints avoided in low-level clock helper | Tracing may recursively read time | Infinite recursion/reentrancy |
There is no safe user option to force a truncated exact scale.
Current main stores clock_freq and clock_ns_per_tick during the process constructor. They are then read by many threads for the process lifetime.
Issue #99 proposes separating:
clock_exact_ns_per_tick immutable mathematical capability
clock_ns_per_tick active startup-selected route
requested policy saved value, possibly restart pending
A live UI change must update only the saved/requested state and display Restart required. The active scale remains unchanged until the next process start.
Fallback is inherent: if exact capability is unavailable, Auto leaves the active scale zero and every call uses muldiv64.
On an exact-capable host:
same clock-read count and nanosecond values
Auto:
exact multiply route count == reads
Generic:
muldiv64 route count == reads
higher clock conversion CPU expected
The benefit is likely visible in aggregate CPU samples rather than one frame marker because get_clock() is spread across many callers.
Disproof/limitation:
- QPC frequency is non-divisible, so both modes are generic;
-
QueryPerformanceCounterdominates conversion; - clock reads are not hot in the selected workload;
- compiler already optimizes the generic ratio effectively on a given toolchain;
- added diagnostic instrumentation costs more than the route difference.
Use sampling or startup-latched low-frequency aggregate counts, not per-call formatted logging.
Because this path is extremely hot, planned evidence should avoid another branch/atomic increment per call in normal production. Options include build-time diagnostics, thread-local sampled counters, or startup route metadata plus external profiles.
| Evidence | Unit | Meaning |
|---|---|---|
| QPC frequency | Hz | Host capability input |
| exact scale capability | ns/tick | Zero when divisibility fails |
| requested/effective route | enum | Startup policy and active branch |
| restart pending | bool | Saved request differs from active route |
| sampled clock-read count | samples/calls | Route frequency, diagnostic only |
get_clock / muldiv64 sampled weight |
ms/% | External profile impact |
| differential arithmetic cases | values | Exact versus generic equality |
| monotonicity violations | count | Must remain zero |
Evidence must record compiler, optimization/LTO, executable hash, and matching DWARF because generated arithmetic cost is build-dependent.
- Assuming common 10-MHz QPC frequency on every host is incorrect.
- Integer division without remainder proof introduces drift.
- Large tick multiplication can overflow if supported-domain assumptions are wrong.
- Writing active scale live can produce discontinuity/races across threads.
- Adding a new hot atomic policy read can erase the optimization being tested.
- Profiling with a Debug build changes timing and generated arithmetic; use optimized Release plus matching DWARF.
- A lower clock-helper sample share may reflect fewer clock calls or different guest progress rather than cheaper conversion.
- Time values must match; this is not an acceptable approximate shortcut.
See issue #99, issue #19, Debugging Lag & Hot Paths, and Debugging Tools & Commands.
- Frequencies: 10 MHz, 1 MHz, 1 GHz, 3,579,545 Hz, zero, and >1-GHz non-divisible cases.
- Exact-capable equality versus
muldiv64for zero/one tick, second boundaries, fixed pseudorandom values, and supported high range. - Non-divisible frequencies never select exact route.
- Monotonic increasing sequence and overflow boundary analysis.
-
clock_start/relative-clock coherence. - Planned Auto/Generic config save/reload and restart-pending behavior.
- Startup apply before worker threads; no live active-scale change.
- Release/O2/LTO disassembly verifying hot path remains QPC + one branch + multiply/reference.
- Same executable separate-start ABBA/BAAB on exact-capable host.
- External sampling of clock/muldiv hot paths, host CPU, guest progress, p95/p99/max.
- GL and Vulkan representative workloads and full applicable XISO correctness.
- Non-Windows build/source control proving no accidental change.
The Generic mode belongs in developer diagnostics. Auto remains safe only because it selects multiplication after a mathematical proof of exact equivalence.
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