Skip to content

perf(host): fast-path a64_cntpct_host on POSIX and restore memory counter queries - #2491

Closed
Erfboom wants to merge 1 commit into
NotRexed:mainfrom
Erfboom:perf/linux-cntpct-fastpath
Closed

Erfboom wants to merge 1 commit into
NotRexed:mainfrom
Erfboom:perf/linux-cntpct-fastpath

Conversation

@Erfboom

@Erfboom Erfboom commented Nov 11, 2026

Copy link
Copy Markdown
Contributor

Problem

  1. a64_cntpct_host (which emulates CNTPCT_EL0 at 31.25 MHz and is queried frequently across game frames and timing loops) performed a 128-bit integer multiplication and 32-bit shift on every single invocation:
    (uint64_t)(((unsigned __int128)(now - base) * mult) >> 32)
    On Linux / POSIX systems, QueryPerformanceFrequency is fixed at 1 GHz (1000000000ULL). Because $1\text{ GHz} / 31.25\text{ MHz} = 32$ exactly ($2^5$), this arithmetic can be replaced with a single right-shift by 5 bits ((now - base) >> 5).
  2. hos_process_memory dynamically looked up K32GetProcessMemoryInfo from kernel32.dll via GetProcAddress. On Linux/POSIX, K32GetProcessMemoryInfo is an inline function provided in <psapi.h> (reading /proc/self/status), so GetProcAddress always failed and process memory counters remained zero.

Solution

  • On non-Windows platforms, simplify a64_cntpct_host to (uint64_t)(now.QuadPart - base) >> 5, eliminating 128-bit multiplication overhead.
  • In hos_process_memory, include <psapi.h> directly on POSIX to populate resident set and private working set metrics accurately for memory logging and HUD display.

Impact

  • Accelerates frequent guest timer/tick queries on Linux.
  • Restores working set and private memory reporting in Linux debug/profiling logs.
  • Verified with tests/run_all.py (359 passed, 0 failed).

…nter queries

- On Linux/POSIX systems where QueryPerformanceFrequency is fixed at 1 GHz, fast-path a64_cntpct_host with (now - base) >> 5 instead of performing 128-bit integer multiplications and 32-bit shifts on every invocation.
- In hos_process_memory, include <psapi.h> directly on POSIX so K32GetProcessMemoryInfo populates working set and private usage accurately rather than returning zeros due to failing GetProcAddress lookup on kernel32.dll.

@Joey-Magnus Joey-Magnus left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this changes anything at runtime, so I'd suggest closing it.

  • a64_cntpct_host: on POSIX, QueryPerformanceFrequency is 1e9, so main's mult is (31250000 << 32) / 1e9 = 2^27, and (d * 2^27) >> 32 is already exactly d >> 5. The PR returns the same values. It saves one 64x64 multiply beside a clock_gettime call that costs far more, and it adds a second copy of the function that silently depends on the 1 GHz frequency.
  • hos_process_memory: the diagnosis is right (GetModuleHandleA("kernel32.dll") is NULL on Linux, so the counters read 0). But its only caller is gpu/render/rx_core.cpp:1477, the WebGPU renderer, and nx2/build.py:886 builds that only when not LINUX. Nothing reads these counters on Linux. The Vulkan back end calls K32GetProcessMemoryInfo directly (vk_texture.cpp:3826).
    If you have a profile showing a64_cntpct_host is hot, I'd like to see it, but the arithmetic can't be the cost.

@ghost ghost left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for looking at this. I checked it against main and agree with @Joey-Magnus's review: neither half changes anything at runtime on Linux.

  • a64_cntpct_host: the POSIX QueryPerformanceFrequency shim (host/posix/windows.h) returns 1e9, so main's mult is already exactly 2^27 and (d * 2^27) >> 32 == d >> 5 for every d. The new branch returns identical values, and the multiply it removes sits next to a clock_gettime that costs far more. It also leaves two copies of the function, one of which silently assumes the shim's 1 GHz frequency. If you have a profile where this shows up, please share it.
  • hos_process_memory: right diagnosis (the GetProcAddress lookup fails on Linux), but its only caller is the WebGPU renderer in gpu/render/rx_core.cpp, which nx2/build.py doesn't build on Linux. The Vulkan back end already calls K32GetProcessMemoryInfo from host/posix/psapi.h directly, so the Linux memory log lines aren't zero today.

No overlap with your other open PRs (#2462, #2467, #2469, #2482, #2487 touch gpu/vk/ and host/posix/, not host/mkw_glue.c). CI hasn't run yet (workflows are waiting for approval), but given the above I'd also suggest closing this one.

@Erfboom

Erfboom commented Nov 11, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @Joey-Magnus and @hckrman101 for checking this against main — agreed that POSIX QueryPerformanceFrequency=1e9 makes (d * 2^27) >> 32 already reduce to d >> 5, and the Vulkan backend queries K32GetProcessMemoryInfo directly rather than going through hos_process_memory. Closing this out!

@Erfboom Erfboom closed this Nov 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants