mirror of
https://git.ryujinx.app/ryubing/ryujinx.git
synced 2025-06-29 02:16:24 +02: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:
parent
21cd4c0c00
commit
1be668e68a
10 changed files with 1000 additions and 49 deletions
|
@ -1,4 +1,5 @@
|
|||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.PreciseSleep;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
@ -23,7 +24,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
|
|||
|
||||
private readonly KernelContext _context;
|
||||
private readonly List<WaitingObject> _waitingObjects;
|
||||
private AutoResetEvent _waitEvent;
|
||||
private IPreciseSleepEvent _waitEvent;
|
||||
private bool _keepRunning;
|
||||
private long _enforceWakeupFromSpinWait;
|
||||
|
||||
|
@ -54,6 +55,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
|
|||
timePoint = long.MaxValue;
|
||||
}
|
||||
|
||||
timePoint = _waitEvent.AdjustTimePoint(timePoint, timeout);
|
||||
|
||||
lock (_context.CriticalSection.Lock)
|
||||
{
|
||||
_waitingObjects.Add(new WaitingObject(schedulerObj, timePoint));
|
||||
|
@ -64,7 +67,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
|
|||
}
|
||||
}
|
||||
|
||||
_waitEvent.Set();
|
||||
_waitEvent.Signal();
|
||||
}
|
||||
|
||||
public void UnscheduleFutureInvocation(IKFutureSchedulerObject schedulerObj)
|
||||
|
@ -83,10 +86,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
|
|||
|
||||
private void WaitAndCheckScheduledObjects()
|
||||
{
|
||||
SpinWait spinWait = new();
|
||||
WaitingObject next;
|
||||
|
||||
using (_waitEvent = new AutoResetEvent(false))
|
||||
using (_waitEvent = PreciseSleepHelper.CreateEvent())
|
||||
{
|
||||
while (_keepRunning)
|
||||
{
|
||||
|
@ -103,30 +105,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
|
|||
|
||||
if (next.TimePoint > timePoint)
|
||||
{
|
||||
long ms = Math.Min((next.TimePoint - timePoint) / PerformanceCounter.TicksPerMillisecond, int.MaxValue);
|
||||
|
||||
if (ms > 0)
|
||||
if (!_waitEvent.SleepUntil(next.TimePoint))
|
||||
{
|
||||
_waitEvent.WaitOne((int)ms);
|
||||
}
|
||||
else
|
||||
{
|
||||
while (Interlocked.Read(ref _enforceWakeupFromSpinWait) != 1 && PerformanceCounter.ElapsedTicks < next.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();
|
||||
}
|
||||
}
|
||||
|
||||
spinWait.Reset();
|
||||
PreciseSleepHelper.SpinWaitUntilTimePoint(next.TimePoint, ref _enforceWakeupFromSpinWait);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -145,7 +126,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
|
|||
}
|
||||
else
|
||||
{
|
||||
_waitEvent.WaitOne();
|
||||
_waitEvent.Sleep();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -212,7 +193,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
|
|||
public void Dispose()
|
||||
{
|
||||
_keepRunning = false;
|
||||
_waitEvent?.Set();
|
||||
_waitEvent?.Signal();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.PreciseSleep;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu;
|
||||
using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap;
|
||||
|
@ -23,9 +25,7 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
|||
|
||||
private readonly Thread _composerThread;
|
||||
|
||||
private readonly Stopwatch _chrono;
|
||||
|
||||
private readonly ManualResetEvent _event = new(false);
|
||||
private readonly AutoResetEvent _event = new(false);
|
||||
private readonly AutoResetEvent _nextFrameEvent = new(true);
|
||||
private long _ticks;
|
||||
private long _ticksPerFrame;
|
||||
|
@ -64,11 +64,9 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
|||
_composerThread = new Thread(HandleComposition)
|
||||
{
|
||||
Name = "SurfaceFlinger.Composer",
|
||||
Priority = ThreadPriority.AboveNormal
|
||||
};
|
||||
|
||||
_chrono = new Stopwatch();
|
||||
_chrono.Start();
|
||||
|
||||
_ticks = 0;
|
||||
_spinTicks = Stopwatch.Frequency / 500;
|
||||
_1msTicks = Stopwatch.Frequency / 1000;
|
||||
|
@ -299,11 +297,11 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
|||
{
|
||||
_isRunning = true;
|
||||
|
||||
long lastTicks = _chrono.ElapsedTicks;
|
||||
long lastTicks = PerformanceCounter.ElapsedTicks;
|
||||
|
||||
while (_isRunning)
|
||||
{
|
||||
long ticks = _chrono.ElapsedTicks;
|
||||
long ticks = PerformanceCounter.ElapsedTicks;
|
||||
|
||||
if (_swapInterval == 0)
|
||||
{
|
||||
|
@ -336,21 +334,16 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
|||
}
|
||||
|
||||
// Sleep if possible. If the time til the next frame is too low, spin wait instead.
|
||||
long diff = _ticksPerFrame - (_ticks + _chrono.ElapsedTicks - ticks);
|
||||
long diff = _ticksPerFrame - (_ticks + PerformanceCounter.ElapsedTicks - ticks);
|
||||
if (diff > 0)
|
||||
{
|
||||
PreciseSleepHelper.SleepUntilTimePoint(_event, PerformanceCounter.ElapsedTicks + diff);
|
||||
|
||||
diff = _ticksPerFrame - (_ticks + PerformanceCounter.ElapsedTicks - ticks);
|
||||
|
||||
if (diff < _spinTicks)
|
||||
{
|
||||
do
|
||||
{
|
||||
// SpinWait is a little more HT/SMT friendly than aggressively updating/checking ticks.
|
||||
// The value of 5 still gives us quite a bit of precision (~0.0003ms variance at worst) while waiting a reasonable amount of time.
|
||||
Thread.SpinWait(5);
|
||||
|
||||
ticks = _chrono.ElapsedTicks;
|
||||
_ticks += ticks - lastTicks;
|
||||
lastTicks = ticks;
|
||||
} while (_ticks < _ticksPerFrame);
|
||||
PreciseSleepHelper.SpinWaitUntilTimePoint(PerformanceCounter.ElapsedTicks + diff);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue