From 7b040e51b078a16e979ad962ba1265f3be4bcb1d Mon Sep 17 00:00:00 2001 From: Mary Date: Sun, 28 Nov 2021 13:15:26 +0100 Subject: [PATCH] kernel: Fix sleep timing accuracy (#2828) * kernel: Fix sleep timing accuracy This commit corrects some mistake while comparing reversing of kernel 13.x with our own. WaitAndCheckScheduledObjects timing accuracy was also improved. * Make KTimeManager.WaitAndCheckScheduledObjects spin wait for sub milliseconds Fix performance regression on Pokemon Let's Go games and possibly others. * Address rip's comment * kernel: Fix issues with timeout of -1 (0xFFFFFFFF) Fixes possible hang on Pokemon DP and possibly others --- Ryujinx.HLE/HOS/Kernel/Common/KTimeManager.cs | 60 +++++++++++++++++-- .../HOS/Kernel/SupervisorCall/Syscall.cs | 32 +++++++++- 2 files changed, 85 insertions(+), 7 deletions(-) diff --git a/Ryujinx.HLE/HOS/Kernel/Common/KTimeManager.cs b/Ryujinx.HLE/HOS/Kernel/Common/KTimeManager.cs index f7a1c24adc..4eb736f2cd 100644 --- a/Ryujinx.HLE/HOS/Kernel/Common/KTimeManager.cs +++ b/Ryujinx.HLE/HOS/Kernel/Common/KTimeManager.cs @@ -8,6 +8,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Common { class KTimeManager : IDisposable { + public static readonly long DefaultTimeIncrementNanoseconds = ConvertGuestTicksToNanoseconds(2); + private class WaitingObject { public IKFutureSchedulerObject Object { get; } @@ -24,6 +26,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Common private readonly List _waitingObjects; private AutoResetEvent _waitEvent; private bool _keepRunning; + private long _enforceWakeupFromSpinWait; public KTimeManager(KernelContext context) { @@ -41,11 +44,16 @@ namespace Ryujinx.HLE.HOS.Kernel.Common public void ScheduleFutureInvocation(IKFutureSchedulerObject schedulerObj, long timeout) { - long timePoint = PerformanceCounter.ElapsedMilliseconds + ConvertNanosecondsToMilliseconds(timeout); + long timePoint = PerformanceCounter.ElapsedTicks + ConvertNanosecondsToHostTicks(timeout); lock (_context.CriticalSection.Lock) { _waitingObjects.Add(new WaitingObject(schedulerObj, timePoint)); + + if (timeout < 1000000) + { + Interlocked.Exchange(ref _enforceWakeupFromSpinWait, 1); + } } _waitEvent.Set(); @@ -61,27 +69,51 @@ namespace Ryujinx.HLE.HOS.Kernel.Common private void WaitAndCheckScheduledObjects() { + SpinWait spinWait = new SpinWait(); + WaitingObject next; + using (_waitEvent = new AutoResetEvent(false)) { while (_keepRunning) { - WaitingObject next; - lock (_context.CriticalSection.Lock) { + Interlocked.Exchange(ref _enforceWakeupFromSpinWait, 0); + next = _waitingObjects.OrderBy(x => x.TimePoint).FirstOrDefault(); } if (next != null) { - long timePoint = PerformanceCounter.ElapsedMilliseconds; + long timePoint = PerformanceCounter.ElapsedTicks; if (next.TimePoint > timePoint) { - _waitEvent.WaitOne((int)(next.TimePoint - timePoint)); + long ms = Math.Min((next.TimePoint - timePoint) / PerformanceCounter.TicksPerMillisecond, int.MaxValue); + + if (ms > 0) + { + _waitEvent.WaitOne((int)ms); + } + else + { + while (Interlocked.Read(ref _enforceWakeupFromSpinWait) != 1 && PerformanceCounter.ElapsedTicks <= next.TimePoint) + { + if (spinWait.NextSpinWillYield) + { + Thread.Yield(); + + spinWait.Reset(); + } + + spinWait.SpinOnce(); + } + + spinWait.Reset(); + } } - bool timeUp = PerformanceCounter.ElapsedMilliseconds >= next.TimePoint; + bool timeUp = PerformanceCounter.ElapsedTicks >= next.TimePoint; if (timeUp) { @@ -119,6 +151,22 @@ namespace Ryujinx.HLE.HOS.Kernel.Common return time * 1000000; } + public static long ConvertNanosecondsToHostTicks(long ns) + { + long nsDiv = ns / 1000000000; + long nsMod = ns % 1000000000; + long tickDiv = PerformanceCounter.TicksPerSecond / 1000000000; + long tickMod = PerformanceCounter.TicksPerSecond % 1000000000; + + long baseTicks = (nsMod * tickMod + PerformanceCounter.TicksPerSecond - 1) / 1000000000; + return (nsDiv * tickDiv) * 1000000000 + nsDiv * tickMod + nsMod * tickDiv + baseTicks; + } + + public static long ConvertGuestTicksToNanoseconds(long ticks) + { + return (long)Math.Ceiling(ticks * (1000000000.0 / 19200000.0)); + } + public static long ConvertHostTicksToTicks(long time) { return (long)((time / (double)PerformanceCounter.TicksPerSecond) * 19200000.0); diff --git a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall.cs b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall.cs index f7b3215f92..8d0d818779 100644 --- a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall.cs +++ b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall.cs @@ -506,6 +506,11 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall return KernelResult.UserCopyFailed; } + if (timeout > 0) + { + timeout += KTimeManager.DefaultTimeIncrementNanoseconds; + } + return ReplyAndReceive(handles, replyTargetHandle, timeout, out handleIndex); } @@ -547,6 +552,11 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall if (result == KernelResult.Success) { + if (timeout > 0) + { + timeout += KTimeManager.DefaultTimeIncrementNanoseconds; + } + while ((result = _context.Synchronization.WaitFor(syncObjs, timeout, out handleIndex)) == KernelResult.Success) { KServerSession session = currentProcess.HandleTable.GetObject(handles[handleIndex]); @@ -644,6 +654,11 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall if (result == KernelResult.Success) { + if (timeout > 0) + { + timeout += KTimeManager.DefaultTimeIncrementNanoseconds; + } + while ((result = _context.Synchronization.WaitFor(syncObjs, timeout, out handleIndex)) == KernelResult.Success) { KServerSession session = currentProcess.HandleTable.GetObject(handles[handleIndex]); @@ -2117,7 +2132,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall } else { - KernelStatic.GetCurrentThread().Sleep(timeout); + KernelStatic.GetCurrentThread().Sleep(timeout + KTimeManager.DefaultTimeIncrementNanoseconds); } } @@ -2458,6 +2473,11 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall } } + if (timeout > 0) + { + timeout += KTimeManager.DefaultTimeIncrementNanoseconds; + } + KernelResult result = _context.Synchronization.WaitFor(syncObjs, timeout, out handleIndex); if (result == KernelResult.PortRemoteClosed) @@ -2541,6 +2561,11 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall KProcess currentProcess = KernelStatic.GetCurrentProcess(); + if (timeout > 0) + { + timeout += KTimeManager.DefaultTimeIncrementNanoseconds; + } + return currentProcess.AddressArbiter.WaitProcessWideKeyAtomic( mutexAddress, condVarAddress, @@ -2571,6 +2596,11 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall KProcess currentProcess = KernelStatic.GetCurrentProcess(); + if (timeout > 0) + { + timeout += KTimeManager.DefaultTimeIncrementNanoseconds; + } + return type switch { ArbitrationType.WaitIfLessThan