diff options
author | riperiperi <rhy3756547@hotmail.com> | 2023-11-30 10:39:42 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-11-30 15:39:42 -0300 |
commit | 1be668e68a1937f2af239e2707ab914286018892 (patch) | |
tree | d3ecb590d444ab9b392928ae1e223930313615e6 /src/Ryujinx.Common/PreciseSleep/PreciseSleepHelper.cs | |
parent | 21cd4c0c00e4a06e399c93419c8f9eff0e663bfb (diff) |
HLE: Add OS-specific precise sleep methods to reduce spinwaiting (#5948)1.1.1094
* feat: add nanosleep for linux and macos
* Add Windows 0.5ms sleep
- Imprecise waits for longer waits with clock alignment
- 1/4 the spin time on vsync timer
* Remove old experiment
* Fix event leak
* Tweaking for MacOS
* Linux tweaks, nanosleep vsync improvement
* Fix overbias
* Cleanup
* Fix realignment
* Add some docs and some cleanup
NanosleepPool needs more, Nanosleep has some benchmark code that needs removed.
* Rename "Microsleep" to "PreciseSleep"
Might have been confused with "microseconds", which no measurement is performed in.
* Remove nanosleep measurement
* Remove unused debug logging
* Nanosleep Pool Documentation
* More cleanup
* Whitespace
* Formatting
* Address Feedback
* Allow SleepUntilTimePoint to take EventWaitHandle
* Remove `_chrono` stopwatch in SurfaceFlinger
* Move spinwaiting logic to PreciseSleepHelper
Technically, these achieve different things, but having them here makes them easier to reuse or tune.
Diffstat (limited to 'src/Ryujinx.Common/PreciseSleep/PreciseSleepHelper.cs')
-rw-r--r-- | src/Ryujinx.Common/PreciseSleep/PreciseSleepHelper.cs | 104 |
1 files changed, 104 insertions, 0 deletions
diff --git a/src/Ryujinx.Common/PreciseSleep/PreciseSleepHelper.cs b/src/Ryujinx.Common/PreciseSleep/PreciseSleepHelper.cs new file mode 100644 index 00000000..3c30a7f6 --- /dev/null +++ b/src/Ryujinx.Common/PreciseSleep/PreciseSleepHelper.cs @@ -0,0 +1,104 @@ +using Ryujinx.Common.SystemInterop; +using System; +using System.Threading; + +namespace Ryujinx.Common.PreciseSleep +{ + public static class PreciseSleepHelper + { + /// <summary> + /// Create a precise sleep event for the current platform. + /// </summary> + /// <returns>A precise sleep event</returns> + public static IPreciseSleepEvent CreateEvent() + { + if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS() || OperatingSystem.IsIOS() || OperatingSystem.IsAndroid()) + { + return new NanosleepEvent(); + } + else if (OperatingSystem.IsWindows()) + { + return new WindowsSleepEvent(); + } + else + { + return new SleepEvent(); + } + } + + /// <summary> + /// Sleeps up to the closest point to the timepoint that the OS reasonably allows. + /// The provided event is used by the timer to wake the current thread, and should not be signalled from any other source. + /// </summary> + /// <param name="evt">Event used to wake this thread</param> + /// <param name="timePoint">Target timepoint in host ticks</param> + public static void SleepUntilTimePoint(EventWaitHandle evt, long timePoint) + { + if (OperatingSystem.IsWindows()) + { + WindowsGranularTimer.Instance.SleepUntilTimePointWithoutExternalSignal(evt, timePoint); + } + else + { + // Events might oversleep by a little, depending on OS. + // We don't want to miss the timepoint, so bias the wait to be lower. + // Nanosleep can possibly handle it better, too. + long accuracyBias = PerformanceCounter.TicksPerMillisecond / 2; + long now = PerformanceCounter.ElapsedTicks + accuracyBias; + long ms = Math.Min((timePoint - now) / PerformanceCounter.TicksPerMillisecond, int.MaxValue); + + if (ms > 0) + { + evt.WaitOne((int)ms); + } + + if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS() || OperatingSystem.IsIOS() || OperatingSystem.IsAndroid()) + { + // Do a nanosleep. + now = PerformanceCounter.ElapsedTicks; + long ns = ((timePoint - now) * 1_000_000) / PerformanceCounter.TicksPerMillisecond; + + Nanosleep.SleepAtMost(ns); + } + } + } + + /// <summary> + /// Spinwait until the given timepoint. If wakeSignal is or becomes 1, return early. + /// Thread is allowed to yield. + /// </summary> + /// <param name="timePoint">Target timepoint in host ticks</param> + /// <param name="wakeSignal">Returns early if this is set to 1</param> + public static void SpinWaitUntilTimePoint(long timePoint, ref long wakeSignal) + { + SpinWait spinWait = new(); + + while (Interlocked.Read(ref wakeSignal) != 1 && PerformanceCounter.ElapsedTicks < timePoint) + { + // Our time is close - don't let SpinWait go off and potentially Thread.Sleep(). + if (spinWait.NextSpinWillYield) + { + Thread.Yield(); + + spinWait.Reset(); + } + else + { + spinWait.SpinOnce(); + } + } + } + + /// <summary> + /// Spinwait until the given timepoint, with no opportunity to wake early. + /// </summary> + /// <param name="timePoint">Target timepoint in host ticks</param> + public static void SpinWaitUntilTimePoint(long timePoint) + { + while (PerformanceCounter.ElapsedTicks < timePoint) + { + Thread.SpinWait(5); + } + } + } +} |