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
This commit is contained in:
Mary 2021-11-28 13:15:26 +01:00 committed by GitHub
parent 786fb04d20
commit 7b040e51b0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 85 additions and 7 deletions

View file

@ -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<WaitingObject> _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();
}
bool timeUp = PerformanceCounter.ElapsedMilliseconds >= next.TimePoint;
spinWait.SpinOnce();
}
spinWait.Reset();
}
}
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);

View file

@ -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<KServerSession>(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<KServerSession>(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