mirror of
https://git.ryujinx.app/ryubing/ryujinx.git
synced 2026-07-03 01:29:05 +00:00
HLE: Add OS-specific precise sleep methods to reduce spinwaiting (#5948)
* 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.
This commit is contained in:
104
src/Ryujinx.Common/PreciseSleep/PreciseSleepHelper.cs
Normal file
104
src/Ryujinx.Common/PreciseSleep/PreciseSleepHelper.cs
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user