Skip to content

perf(libkernel): wake only the event flag waiters that a set, cancel or delete releases - #11891

Open
imellag wants to merge 6 commits into
boykopovar:mainfrom
imellag:perf/libkernel-event-flag-wake-satisfied
Open

imellag wants to merge 6 commits into
boykopovar:mainfrom
imellag:perf/libkernel-event-flag-wake-satisfied

Conversation

@imellag

@imellag imellag commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

What

sceKernelSetEventFlag already marks only the waiters whose pattern it satisfies, but then calls NotifyAll on the flag's one condition, so every thread waiting on the flag wakes, takes the flag's mutex, finds itself still pending and sleeps again. Each waiter that leaves also called NotifyAll, which only sceKernelDeleteEventFlag needs (it waits for the waiter count to reach 0). A flag with many waiters, like a job system's wake-up flag, pays two rounds of wake-ups and mutex contention for every set.

Each waiter now sleeps on its own condition:

  • sceKernelSetEventFlag wakes only the waiters it satisfies;
  • sceKernelCancelEventFlag and sceKernelDeleteEventFlag wake the waiters they release, which is all pending ones;
  • a leaving waiter notifies the flag's condition only while the flag is being deleted.

What a wait returns is unchanged. This is built on #10806, whose per-waiter outcome (Pending until a set, cancel or delete settles it) tells each operation exactly which waiters it released.

guest_event_flag_wake is new. It checks that each set releases only the waiter whose bit it sets while the others keep waiting, that an AND waiter waits for every bit, that a timeout leaves the other waiters waiting, and that cancel and delete release all 8 waiters with ECANCELED and EACCES. A missed wake-up on any of these paths shows up as a timeout.

Tested

Apple M4 Pro, macOS 27.0.1, MoltenVK 1.4.2, x86-64 Release under Rosetta 2. main doesn't build libkernel on macOS by itself, so the tree is main 3fea2a937b with the 8 open PRs listed on #1 (#7631, #7804, #10552, #11249, #11447, #11554, #10669, #11047) and #10806. Base 255c335e9f87 is that tree, and the change 75772816241e is the base plus this commit (fork branches bench/evf-pr-base and bench/evf-pr on imellag/AnyPS5).

  • guest_event_flag_wake, guest_event_flag_released_waiter (fix(libkernel): keep the result of an event flag wait that was already satisfied when it is cancelled or deleted #10806's) and guest_kernel_errors pass 10 of 10 runs, and 10 of 10 again with the two event flag tests running in parallel (-j4).

  • Planet of Lana (PPSA19450), village after New Game, mimalloc injected in both arms, same shader cache. Its 13 job threads wait on event flags. Time Profiler (xctrace), 10 s from second 95:

    CPU time per 10 s base this PR
    in sceKernelWaitEventFlag 2893 ms 1540 ms (-47%)
    in sceKernelSetEventFlag 199 ms 349 ms (the setter now wakes each waiter it releases)
    of which _pthread_mutex_firstfit_lock_slow / unlock_slow under the wait 984 / 424 ms 442 / 220 ms
  • Frame rate: unchanged within noise. Alternating 120 s runs, fps from second 85:

    • base: 30.61, 30.18, 30.62, 28.55;
    • this PR: 30.74, 30.69, 28.54.

    One more PR run stayed on the title screen and is left out. Lana's frame is bound by the guest building the next frame's commands after the previous one's end-of-pipe event, so the CPU saved doesn't shorten it. Titles whose event flag waiters are on the critical path may see more.

  • Summary lines and traces (APS5_TRACE_SECONDS=95:10 APS5_TRACE_EVENTS=1048576) are attached below. tools/trace_digest.py:

    • base: 304 presents, frame p50 32.6 / p90 34.6 / p99 40.1 ms;
    • this PR: 308 presents, frame p50 32.3 / p90 33.9 / p99 39.4 ms.
{"commit":"255c335e9f87","dirty":false,"env":{"APS5_ASYNC_PIPELINES":"1","APS5_NO_PAD_INPUT":"1","APS5_PAD_INPUT_FILE":"<path>","APS5_TRACE_DIR":"<path>","APS5_TRACE_EVENTS":"1048576","APS5_TRACE_SECONDS":"95:10","APS5_VIDEOOUT_TELEMETRY_DIR":"<path>"},"gpu":"Apple M4 Pro","driver":"MoltenVK 1.4.2","os":"Darwin 27.0.0","cpu":"VirtualApple @ 2.50GHz","cpu_threads":12,"memory_mib":24576,"title_id":"PPSA19450","title":"Planet of Lana","content_version":"01.010.000","resolution":"3840x2160","window":{"spec":"seconds 95:10","first_present":3653,"ms":10000.398},"presents":304,"frame_ms":{"p50":32.554,"p90":34.574,"p99":40.066,"max":49.286},"gpu_busy_ms_per_present":23.253,"worker_busy_pct":{"queue worker 0x0":59.461},"top_phases_ms_per_frame":{"Driver.Packet":19.286,"Driver.Packet.draw":15.155,"Driver.Draw":15.005,"Graphics.Draw":13.162,"VideoOut.Flip":12.869,"Driver.Present":12.094,"VideoOut.Flip.present":12.061,"Vulkan.Retire":11.988},"trace_dropped":false,"trace":"aps5-trace-255c335e9f87-41986.json.gz"}
{"commit":"75772816241e","dirty":false,"env":{"APS5_ASYNC_PIPELINES":"1","APS5_NO_PAD_INPUT":"1","APS5_PAD_INPUT_FILE":"<path>","APS5_TRACE_DIR":"<path>","APS5_TRACE_EVENTS":"1048576","APS5_TRACE_SECONDS":"95:10","APS5_VIDEOOUT_TELEMETRY_DIR":"<path>"},"gpu":"Apple M4 Pro","driver":"MoltenVK 1.4.2","os":"Darwin 27.0.0","cpu":"VirtualApple @ 2.50GHz","cpu_threads":12,"memory_mib":24576,"title_id":"PPSA19450","title":"Planet of Lana","content_version":"01.010.000","resolution":"3840x2160","window":{"spec":"seconds 95:10","first_present":3666,"ms":10018.677},"presents":308,"frame_ms":{"p50":32.274,"p90":33.877,"p99":39.434,"max":54.684},"gpu_busy_ms_per_present":21.134,"worker_busy_pct":{"queue worker 0x0":59.993},"top_phases_ms_per_frame":{"Driver.Packet":19.238,"Driver.Packet.draw":15.265,"Driver.Draw":15.111,"Graphics.Draw":13.297,"VideoOut.Flip":12.612,"Driver.Present":11.846,"VideoOut.Flip.present":11.814,"Vulkan.Retire":11.743},"trace_dropped":false,"trace":"aps5-trace-75772816241e-48919.json.gz"}

Not run on Linux or Windows. The change uses the same TimedWait::Condition as before, only one per waiter.

tools/check_conventions.py --base upstream/main passes. tools/pr_overlap.py finds conflicting lines only with #10806, which this builds on. #8285 touches EventFlag.cpp only in its include list.

aps5-summary-base-lana.txt
aps5-summary-pr-lana.txt
pr-evf-trace-base-lana.json.gz
pr-evf-trace-pr-lana.json.gz

Checklist

MarcZX10 and others added 6 commits October 27, 2026 12:18
… out

The 1 ms timeout can expire and unlink the waiter before the parent polls the waiter count, so WaitForWaiters reached its deadline with a count of 0. The join already orders the timeout result before the later set.
…re the poll

The set with CLEAR_PAT clears the bit under the lock, so remaining is always 0 and the waiter's result always holds; the guards and the consumed counter could never fail.
@github-actions github-actions Bot added the perf Performance change label Oct 27, 2026

@MarcZX10 MarcZX10 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 reviewed the incremental wake-routing change and its test coverage. The per-waiter condition is protected by the same flag mutex as its outcome, and the waiter remains alive until it has reacquired that mutex and unlinked itself. Set, cancel and delete notify only outcomes they settle; deletion still waits for all waiters to exit.

I built both new event-flag test targets from this PR's exact changed files. Native CTest cannot run them on this host because its CPU guard requires AVX2/FMA; both executables pass under QEMU Haswell. The hosted Windows build passes. I also downloaded both attached traces and summary lines and reran tools/trace_digest.py; the reported capture metadata and digest values match the PR. The same-machine captures show lower wait time, with no claimed frame-rate gain.

This branch includes the five commits from #10806 before the performance commit, and #10806 is still open. I am leaving this as a comment rather than approving the full stacked diff until I have reviewed that prerequisite too. Linux full CI is skipped; the focused local test used Linux with CPU emulation, while the performance measurement is on macOS.

@github-actions

github-actions Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

Some comparisons could not be completed because a pull request conflicts with the base branch. Review these shared files manually before concluding that there is no overlap.

Pull request Shared files not compared
PR 4563 core/libs/prx/libkernel/CMakeLists.txt
PR 5021 core/libs/prx/libkernel/CMakeLists.txt
PR 6936 core/libs/prx/libkernel/CMakeLists.txt
PR 7192 core/libs/prx/libkernel/CMakeLists.txt
PR 9019 core/libs/prx/libkernel/CMakeLists.txt
PR 9156 core/libs/prx/libkernel/CMakeLists.txt
PR 9293 core/libs/prx/libkernel/CMakeLists.txt
PR 9297 core/libs/prx/libkernel/CMakeLists.txt
PR 9302 core/libs/prx/libkernel/CMakeLists.txt
PR 9383 core/libs/prx/libkernel/CMakeLists.txt
PR 9389 core/libs/prx/libkernel/CMakeLists.txt
PR 9557 core/libs/prx/libkernel/CMakeLists.txt
PR 9566 core/libs/prx/libkernel/CMakeLists.txt
PR 10645 core/libs/prx/libkernel/CMakeLists.txt
PR 11002 core/libs/prx/libkernel/CMakeLists.txt
PR 11625 core/libs/prx/libkernel/CMakeLists.txt
PR 11969 core/libs/prx/libkernel/CMakeLists.txt
PR 12296 core/libs/prx/libkernel/CMakeLists.txt

@JJenkx

JJenkx commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Windows run at this PR's exact head 6bec8e68bf (6bec8e68bf4833116057c20509a2ba865c7b8ab9), not merged onto anything else. Its merge base with main is 3fea2a937b.

Windows 11 Enterprise 10.0.22621, Ryzen 9 5950X (32 threads), WinLibs GCC 15.2.0 UCRT r7, Ninja, Release, BUILD_TESTING=ON, in a fresh build directory.

All three pass in each of 10 runs, with no failed, skipped or not-run tests. The runs were serial, ctest without -j:

  • guest_event_flag_wake (0.63 to 0.64 s)
  • guest_event_flag_released_waiter (0.29 to 0.31 s)
  • guest_kernel_errors (0.14 to 0.15 s)

The parallel -j4 repetition from the PR description was not repeated here, and no title or console was run. I also read the EventFlag.cpp diff. The satisfied outcome is checked before the timeout, so a waiter satisfied at its deadline returns OK. Delete still waits on _changed for the waiter count, which the guard notifies only once the flag is deleted. I found nothing to change.

AI-assisted: yes (Claude Code).

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

build libkernel perf Performance change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants