Repository navigation
Keep the graphics lock between graphics hooks (main thread) - #220
Merged
Merged
Conversation
A race frame enters about 15,000-20,000 graphics hooks on the main thread, each taking and giving back the graphics lock (an uncontended std::mutex: 33 ns a pair on an AYN Thor, 18 ns on the desktop). SFR_GRAPHICS_LOCK_STATS=1 counted it over full races on Windows and the Thor: no other thread takes the lock in a race and no acquisition ever waited, and 91% of them follow the previous release with no import, other hook or permit checkpoint in between. SFR_GRAPHICS_LOCK_DEFER (on unless 0): the main thread keeps the lock when a graphics hook returns and the next one takes it over without an atomic. Outside a hook it is then held only while generated code runs, and it is given back before anything that may wait or take another lock: an import, any other hook (HookScope, SFR_CONCURRENT_HOOK, the observed entry of a hook), the permit's checkpoint (at least every 256 entries and loop labels), a slow memory access and traced_host_wait. While another thread waits for the lock it is given back at every hook's return as before; HostGraphicsScope takes a kept lock over. The lock order (menu, input, graphics; graphics before the permits) is unchanged. The register save and restore helpers, which never wait, use SFR_REGISTER_HOOK without the check. Also: parallel_function_entry returns before reading the permit's thread_local when the subsystem locks leave nothing to do (every graphics hook paid that TLS access on Android). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lock # Conflicts: # docs/performance.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #215, which measured the graphics lock as ~2% of the main thread on an AYN Thor and left it alone.
What the lock protects and who takes it
The graphics lock keeps one thread at a time in the native device: every Direct3D replacement (
SFR_GRAPHICS_HOOK,SFR_GRAPHICS_HOST_HOOK) takes it at its observed entry and gives it back when itsHookScopeends.HostGraphicsScopetakes it for host code outside those hooks that changes the device (synchronize_resource_memory, the renderer'sinvalidateafterMmFreePhysicalMemory, the Avatar model's draw). Lock order: menu, input, graphics; graphics before the permits.New
SFR_GRAPHICS_LOCK_STATS=1printsGRAPHICS_LOCKevery 120 frames. Full Dolphin Resort races on Windows and the Thor:HostGraphicsScopeIn a race the lock belongs to the main thread alone and is never contended. The cost is the uncontended pair itself: 33 ns on the Thor, 18 ns on the desktop. That compares with 14.5 ns and 6 ns for a bare atomic pair. Bionic's
pthread_mutex_trylock/unlockgo through the PLT, check the mutex type and then run the outline atomics__aarch64_cas2_acqand__aarch64_swp2_rel.Change
SFR_GRAPHICS_LOCK_DEFER(on by default;0turns it off): when a graphics hook returns, the main thread keeps the lock, and the next graphics hook takes it over without an atomic. Outside a hook the lock is then held only while generated code runs. It is given back before anything that may wait or take another lock:dispatch_import)HookScope,SFR_CONCURRENT_HOOK, or the observed entry of a hook, before its menu or input locktraced_host_waitHostGraphicsScopetakes a kept lock over, so nothing inside it gives the lock back early. While another thread waits for the lock, it is given back at every hook's return as before, and a waiter never waits longer than the main thread's next checkpoint. The lock order does not change, and the thread never waits or takes another lock while it keeps the graphics lock outside a hook.Everything else that generated code calls is a memory, vector or reservation helper (leaf locks only),
read_time_base, the job mid-asm hooks (whose waits go throughtraced_host_wait) orsynchronize_resource_memory. Register save/restore helpers never wait, so they becomeSFR_REGISTER_HOOKand skip the check. The new flags sit inGuestEntryState's tail padding: the struct stays 48 bytes (static_assert), so the generated code's offsets do not move. With the lock kept, a race frame takes it ~1,100-1,500 times instead of ~16,000. Also:parallel_function_entrynow returns before reading the permit's thread_local when the subsystem locks leave it nothing to do.Thor A/B
simpleperf on the main thread, 40 s of the moving race from about frame 13800. Both runs use the same APK, one with
SFR_GRAPHICS_LOCK_DEFER=0:__aarch64_cas2_acq+__aarch64_swp2_relpthread_mutex_*+std::mutexThe remaining 0.18 ms is other locks: critical sections, the layout's shared_mutex and events.
HOST_THREADS frame times (frames 13000-21000) are too noisy to show the ~0.34 ms. Between runs the main thread moves between the X3 and the A715s, and drawn frames sit at the 16.7 ms budget, so
skippedswings from 34 to 176 for the same APK:Tests
guest_threadshit its 10 s timeout under-j 8while the machine was loaded; it then passed alone and 30 times in a row.LOCK_LONG_WAIT: four races with the lock kept and one with it off.Risks
SFR_CONCURRENT_HOOKorSFR_HOOKcovers itself through its macro, but a new mid-asm hook that waits must usetraced_host_waitor give the lock back itself.SFR_GRAPHICS_LOCK_DEFER=0restores the old behaviour.🤖 Generated with Claude Code