Ryujinx/Ryujinx.HLE/HOS/Kernel/Common/KTimeManager.cs
jhorv 666e05f5cb
Reducing Memory Allocations 202303 (#4624)
* use ArrayPool, avoid 6000-7000 allocs/sec of runtime

* use ArrayPool, avoid ~7k allocs/second during game execution

* use ArrayPool, avoid ~3000 allocs/sec during game execution

* use MemoryPool, reduce 0.5 MB/sec of new allocations during game execution

* avoid over-allocation by setting List<> Capacity when known

* remove LINQ in KTimeManager.UnscheduleFutureInvocation

* KTimeManager - avoid spinning one more time when the time has arrived

* KTimeManager - let SpinWait decide when to Thread.Yield(), and don't SpinOnce() immediately after Thread.Yield()

* use MemoryPool, reduce ~175k bytes/sec allocation during game execution

* IpcService - call commands via dynamic methods instead of reflection .Invoke(). Faster to call and with fewer allocations because parameters can be passed directly instead of as an array

* Make ButtonMappingEntry a record struct to avoid allocations. Set the List<ButtonMappingEntry> capacity according to use.

* add MemoryBuffer type for working with MemoryPool<byte>

* update changes to use MemoryBuffer

* make parameter ReadOnlySpan instead of Span

* whitespace fix

* Revert "IpcService - call commands via dynamic methods instead of reflection .Invoke(). Faster to call and with fewer allocations because parameters can be passed directly instead of as an array"

This reverts commit f2c698bdf65f049e8481c9f2ec7138d9b9a8261d.

* tweak KTimeManager spin behavior

* replace MemoryBuffer with ByteMemoryPool modeled after System.Buffers.ArrayMemoryPool<T>

* make ByteMemoryPoolBuffer responsible for renting memory
2023-04-24 02:06:23 +00:00

218 lines
No EOL
7.1 KiB
C#

using Ryujinx.Common;
using System;
using System.Collections.Generic;
using System.Threading;
namespace Ryujinx.HLE.HOS.Kernel.Common
{
class KTimeManager : IDisposable
{
public static readonly long DefaultTimeIncrementNanoseconds = ConvertGuestTicksToNanoseconds(2);
private class WaitingObject
{
public IKFutureSchedulerObject Object { get; }
public long TimePoint { get; }
public WaitingObject(IKFutureSchedulerObject schedulerObj, long timePoint)
{
Object = schedulerObj;
TimePoint = timePoint;
}
}
private readonly KernelContext _context;
private readonly List<WaitingObject> _waitingObjects;
private AutoResetEvent _waitEvent;
private bool _keepRunning;
private long _enforceWakeupFromSpinWait;
private const long NanosecondsPerSecond = 1000000000L;
private const long NanosecondsPerMillisecond = 1000000L;
public KTimeManager(KernelContext context)
{
_context = context;
_waitingObjects = new List<WaitingObject>();
_keepRunning = true;
Thread work = new Thread(WaitAndCheckScheduledObjects)
{
Name = "HLE.TimeManager"
};
work.Start();
}
public void ScheduleFutureInvocation(IKFutureSchedulerObject schedulerObj, long timeout)
{
long startTime = PerformanceCounter.ElapsedTicks;
long timePoint = startTime + ConvertNanosecondsToHostTicks(timeout);
if (timePoint < startTime)
{
timePoint = long.MaxValue;
}
lock (_context.CriticalSection.Lock)
{
_waitingObjects.Add(new WaitingObject(schedulerObj, timePoint));
if (timeout < NanosecondsPerMillisecond)
{
Interlocked.Exchange(ref _enforceWakeupFromSpinWait, 1);
}
}
_waitEvent.Set();
}
public void UnscheduleFutureInvocation(IKFutureSchedulerObject schedulerObj)
{
lock (_context.CriticalSection.Lock)
{
for (int index = _waitingObjects.Count - 1; index >= 0; index--)
{
if (_waitingObjects[index].Object == schedulerObj)
{
_waitingObjects.RemoveAt(index);
}
}
}
}
private void WaitAndCheckScheduledObjects()
{
SpinWait spinWait = new SpinWait();
WaitingObject next;
using (_waitEvent = new AutoResetEvent(false))
{
while (_keepRunning)
{
lock (_context.CriticalSection.Lock)
{
Interlocked.Exchange(ref _enforceWakeupFromSpinWait, 0);
next = GetNextWaitingObject();
}
if (next != null)
{
long timePoint = PerformanceCounter.ElapsedTicks;
if (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)
{
// 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();
}
}
bool timeUp = PerformanceCounter.ElapsedTicks >= next.TimePoint;
if (timeUp)
{
lock (_context.CriticalSection.Lock)
{
if (_waitingObjects.Remove(next))
{
next.Object.TimeUp();
}
}
}
}
else
{
_waitEvent.WaitOne();
}
}
}
}
private WaitingObject GetNextWaitingObject()
{
WaitingObject selected = null;
long lowestTimePoint = long.MaxValue;
for (int index = _waitingObjects.Count - 1; index >= 0; index--)
{
WaitingObject current = _waitingObjects[index];
if (current.TimePoint <= lowestTimePoint)
{
selected = current;
lowestTimePoint = current.TimePoint;
}
}
return selected;
}
public static long ConvertNanosecondsToMilliseconds(long time)
{
time /= NanosecondsPerMillisecond;
if ((ulong)time > int.MaxValue)
{
return int.MaxValue;
}
return time;
}
public static long ConvertMillisecondsToNanoseconds(long time)
{
return time * NanosecondsPerMillisecond;
}
public static long ConvertNanosecondsToHostTicks(long ns)
{
long nsDiv = ns / NanosecondsPerSecond;
long nsMod = ns % NanosecondsPerSecond;
long tickDiv = PerformanceCounter.TicksPerSecond / NanosecondsPerSecond;
long tickMod = PerformanceCounter.TicksPerSecond % NanosecondsPerSecond;
long baseTicks = (nsMod * tickMod + PerformanceCounter.TicksPerSecond - 1) / NanosecondsPerSecond;
return (nsDiv * tickDiv) * NanosecondsPerSecond + 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);
}
public void Dispose()
{
_keepRunning = false;
_waitEvent?.Set();
}
}
}