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 class KTimeManager : IDisposable
{ {
public static readonly long DefaultTimeIncrementNanoseconds = ConvertGuestTicksToNanoseconds(2);
private class WaitingObject private class WaitingObject
{ {
public IKFutureSchedulerObject Object { get; } public IKFutureSchedulerObject Object { get; }
@ -24,6 +26,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
private readonly List<WaitingObject> _waitingObjects; private readonly List<WaitingObject> _waitingObjects;
private AutoResetEvent _waitEvent; private AutoResetEvent _waitEvent;
private bool _keepRunning; private bool _keepRunning;
private long _enforceWakeupFromSpinWait;
public KTimeManager(KernelContext context) public KTimeManager(KernelContext context)
{ {
@ -41,11 +44,16 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
public void ScheduleFutureInvocation(IKFutureSchedulerObject schedulerObj, long timeout) public void ScheduleFutureInvocation(IKFutureSchedulerObject schedulerObj, long timeout)
{ {
long timePoint = PerformanceCounter.ElapsedMilliseconds + ConvertNanosecondsToMilliseconds(timeout); long timePoint = PerformanceCounter.ElapsedTicks + ConvertNanosecondsToHostTicks(timeout);
lock (_context.CriticalSection.Lock) lock (_context.CriticalSection.Lock)
{ {
_waitingObjects.Add(new WaitingObject(schedulerObj, timePoint)); _waitingObjects.Add(new WaitingObject(schedulerObj, timePoint));
if (timeout < 1000000)
{
Interlocked.Exchange(ref _enforceWakeupFromSpinWait, 1);
}
} }
_waitEvent.Set(); _waitEvent.Set();
@ -61,27 +69,51 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
private void WaitAndCheckScheduledObjects() private void WaitAndCheckScheduledObjects()
{ {
SpinWait spinWait = new SpinWait();
WaitingObject next;
using (_waitEvent = new AutoResetEvent(false)) using (_waitEvent = new AutoResetEvent(false))
{ {
while (_keepRunning) while (_keepRunning)
{ {
WaitingObject next;
lock (_context.CriticalSection.Lock) lock (_context.CriticalSection.Lock)
{ {
Interlocked.Exchange(ref _enforceWakeupFromSpinWait, 0);
next = _waitingObjects.OrderBy(x => x.TimePoint).FirstOrDefault(); next = _waitingObjects.OrderBy(x => x.TimePoint).FirstOrDefault();
} }
if (next != null) if (next != null)
{ {
long timePoint = PerformanceCounter.ElapsedMilliseconds; long timePoint = PerformanceCounter.ElapsedTicks;
if (next.TimePoint > timePoint) 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) if (timeUp)
{ {
@ -119,6 +151,22 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
return time * 1000000; 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) public static long ConvertHostTicksToTicks(long time)
{ {
return (long)((time / (double)PerformanceCounter.TicksPerSecond) * 19200000.0); return (long)((time / (double)PerformanceCounter.TicksPerSecond) * 19200000.0);

View File

@ -506,6 +506,11 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return KernelResult.UserCopyFailed; return KernelResult.UserCopyFailed;
} }
if (timeout > 0)
{
timeout += KTimeManager.DefaultTimeIncrementNanoseconds;
}
return ReplyAndReceive(handles, replyTargetHandle, timeout, out handleIndex); return ReplyAndReceive(handles, replyTargetHandle, timeout, out handleIndex);
} }
@ -547,6 +552,11 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
if (result == KernelResult.Success) if (result == KernelResult.Success)
{ {
if (timeout > 0)
{
timeout += KTimeManager.DefaultTimeIncrementNanoseconds;
}
while ((result = _context.Synchronization.WaitFor(syncObjs, timeout, out handleIndex)) == KernelResult.Success) while ((result = _context.Synchronization.WaitFor(syncObjs, timeout, out handleIndex)) == KernelResult.Success)
{ {
KServerSession session = currentProcess.HandleTable.GetObject<KServerSession>(handles[handleIndex]); KServerSession session = currentProcess.HandleTable.GetObject<KServerSession>(handles[handleIndex]);
@ -644,6 +654,11 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
if (result == KernelResult.Success) if (result == KernelResult.Success)
{ {
if (timeout > 0)
{
timeout += KTimeManager.DefaultTimeIncrementNanoseconds;
}
while ((result = _context.Synchronization.WaitFor(syncObjs, timeout, out handleIndex)) == KernelResult.Success) while ((result = _context.Synchronization.WaitFor(syncObjs, timeout, out handleIndex)) == KernelResult.Success)
{ {
KServerSession session = currentProcess.HandleTable.GetObject<KServerSession>(handles[handleIndex]); KServerSession session = currentProcess.HandleTable.GetObject<KServerSession>(handles[handleIndex]);
@ -2117,7 +2132,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
} }
else 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); KernelResult result = _context.Synchronization.WaitFor(syncObjs, timeout, out handleIndex);
if (result == KernelResult.PortRemoteClosed) if (result == KernelResult.PortRemoteClosed)
@ -2541,6 +2561,11 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
KProcess currentProcess = KernelStatic.GetCurrentProcess(); KProcess currentProcess = KernelStatic.GetCurrentProcess();
if (timeout > 0)
{
timeout += KTimeManager.DefaultTimeIncrementNanoseconds;
}
return currentProcess.AddressArbiter.WaitProcessWideKeyAtomic( return currentProcess.AddressArbiter.WaitProcessWideKeyAtomic(
mutexAddress, mutexAddress,
condVarAddress, condVarAddress,
@ -2571,6 +2596,11 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
KProcess currentProcess = KernelStatic.GetCurrentProcess(); KProcess currentProcess = KernelStatic.GetCurrentProcess();
if (timeout > 0)
{
timeout += KTimeManager.DefaultTimeIncrementNanoseconds;
}
return type switch return type switch
{ {
ArbitrationType.WaitIfLessThan ArbitrationType.WaitIfLessThan