aboutsummaryrefslogtreecommitdiff
path: root/Ryujinx.HLE/HOS/Services/ServerBase.cs
diff options
context:
space:
mode:
authorjhorv <38920027+jhorv@users.noreply.github.com>2023-03-17 08:14:50 -0400
committerGitHub <noreply@github.com>2023-03-17 13:14:50 +0100
commit5131b71437b36f9b876046a2fddd5465652e0f10 (patch)
treea1b8647b1f948e3f88708d9ba41b2d1338f34de9 /Ryujinx.HLE/HOS/Services/ServerBase.cs
parent7870423671cba17c8aad9bb93a67c84bda441366 (diff)
Reducing memory allocations (#4537)1.1.668
* add RecyclableMemoryStream dependency and MemoryStreamManager * organize BinaryReader/BinaryWriter extensions * add StreamExtensions to reduce need for BinaryWriter * simple replacments of MemoryStream with RecyclableMemoryStream * add write ReadOnlySequence<byte> support to IVirtualMemoryManager * avoid 0-length array creation * rework IpcMessage and related types to greatly reduce memory allocation by using RecylableMemoryStream, keeping streams around longer, avoiding their creation when possible, and avoiding creation of BinaryReader and BinaryWriter when possible * reduce LINQ-induced memory allocations with custom methods to query KPriorityQueue * use RecyclableMemoryStream in StreamUtils, and use StreamUtils in EmbeddedResources * add constants for nanosecond/millisecond conversions * code formatting * XML doc adjustments * fix: StreamExtension.WriteByte not writing non-zero values for lengths <= 16 * XML Doc improvements. Implement StreamExtensions.WriteByte() block writes for large-enough count values. * add copyless path for StreamExtension.Write(ReadOnlySpan<int>) * add default implementation of IVirtualMemoryManager.Write(ulong, ReadOnlySequence<byte>); remove previous explicit implementations * code style fixes * remove LINQ completely from KScheduler/KPriorityQueue by implementing a custom struct-based enumerator
Diffstat (limited to 'Ryujinx.HLE/HOS/Services/ServerBase.cs')
-rw-r--r--Ryujinx.HLE/HOS/Services/ServerBase.cs274
1 files changed, 146 insertions, 128 deletions
diff --git a/Ryujinx.HLE/HOS/Services/ServerBase.cs b/Ryujinx.HLE/HOS/Services/ServerBase.cs
index d4382a64..619f5448 100644
--- a/Ryujinx.HLE/HOS/Services/ServerBase.cs
+++ b/Ryujinx.HLE/HOS/Services/ServerBase.cs
@@ -1,3 +1,5 @@
+using Ryujinx.Common;
+using Ryujinx.Common.Memory;
using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.Kernel;
using Ryujinx.HLE.HOS.Kernel.Ipc;
@@ -5,6 +7,7 @@ using Ryujinx.HLE.HOS.Kernel.Process;
using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.Horizon.Common;
using System;
+using System.Buffers;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.IO;
@@ -37,14 +40,27 @@ namespace Ryujinx.HLE.HOS.Services
private readonly Dictionary<int, IpcService> _sessions = new Dictionary<int, IpcService>();
private readonly Dictionary<int, Func<IpcService>> _ports = new Dictionary<int, Func<IpcService>>();
+ private readonly MemoryStream _requestDataStream;
+ private readonly BinaryReader _requestDataReader;
+
+ private readonly MemoryStream _responseDataStream;
+ private readonly BinaryWriter _responseDataWriter;
+
public ManualResetEvent InitDone { get; }
public string Name { get; }
public Func<IpcService> SmObjectFactory { get; }
public ServerBase(KernelContext context, string name, Func<IpcService> smObjectFactory = null)
{
- InitDone = new ManualResetEvent(false);
_context = context;
+
+ _requestDataStream = MemoryStreamManager.Shared.GetStream();
+ _requestDataReader = new BinaryReader(_requestDataStream);
+
+ _responseDataStream = MemoryStreamManager.Shared.GetStream();
+ _responseDataWriter = new BinaryWriter(_responseDataStream);
+
+ InitDone = new ManualResetEvent(false);
Name = name;
SmObjectFactory = smObjectFactory;
@@ -110,15 +126,15 @@ namespace Ryujinx.HLE.HOS.Services
while (true)
{
- int[] portHandles = _portHandles.ToArray();
- int[] sessionHandles = _sessionHandles.ToArray();
- int[] handles = new int[portHandles.Length + sessionHandles.Length];
+ int handleCount = _portHandles.Count + _sessionHandles.Count;
- portHandles.CopyTo(handles, 0);
- sessionHandles.CopyTo(handles, portHandles.Length);
+ int[] handles = ArrayPool<int>.Shared.Rent(handleCount);
+
+ _portHandles.CopyTo(handles, 0);
+ _sessionHandles.CopyTo(handles, _portHandles.Count);
// We still need a timeout here to allow the service to pick up and listen new sessions...
- var rc = _context.Syscall.ReplyAndReceive(out int signaledIndex, handles, replyTargetHandle, 1000000L);
+ var rc = _context.Syscall.ReplyAndReceive(out int signaledIndex, handles.AsSpan(0, handleCount), replyTargetHandle, 1000000L);
thread.HandlePostSyscall();
@@ -129,7 +145,7 @@ namespace Ryujinx.HLE.HOS.Services
replyTargetHandle = 0;
- if (rc == Result.Success && signaledIndex >= portHandles.Length)
+ if (rc == Result.Success && signaledIndex >= _portHandles.Count)
{
// We got a IPC request, process it, pass to the appropriate service if needed.
int signaledHandle = handles[signaledIndex];
@@ -156,6 +172,8 @@ namespace Ryujinx.HLE.HOS.Services
_selfProcess.CpuMemory.Write(messagePtr + 0x4, 2 << 10);
_selfProcess.CpuMemory.Write(messagePtr + 0x8, heapAddr | ((ulong)PointerBufferSize << 48));
}
+
+ ArrayPool<int>.Shared.Return(handles);
}
Dispose();
@@ -166,13 +184,9 @@ namespace Ryujinx.HLE.HOS.Services
KProcess process = KernelStatic.GetCurrentProcess();
KThread thread = KernelStatic.GetCurrentThread();
ulong messagePtr = thread.TlsAddress;
- ulong messageSize = 0x100;
- byte[] reqData = new byte[messageSize];
+ IpcMessage request = ReadRequest(process, messagePtr);
- process.CpuMemory.Read(messagePtr, reqData);
-
- IpcMessage request = new IpcMessage(reqData, (long)messagePtr);
IpcMessage response = new IpcMessage();
ulong tempAddr = recvListAddr;
@@ -202,158 +216,157 @@ namespace Ryujinx.HLE.HOS.Services
bool shouldReply = true;
bool isTipcCommunication = false;
- using (MemoryStream raw = new MemoryStream(request.RawData))
+ _requestDataStream.SetLength(0);
+ _requestDataStream.Write(request.RawData);
+ _requestDataStream.Position = 0;
+
+ if (request.Type == IpcMessageType.HipcRequest ||
+ request.Type == IpcMessageType.HipcRequestWithContext)
{
- BinaryReader reqReader = new BinaryReader(raw);
+ response.Type = IpcMessageType.HipcResponse;
- if (request.Type == IpcMessageType.HipcRequest ||
- request.Type == IpcMessageType.HipcRequestWithContext)
- {
- response.Type = IpcMessageType.HipcResponse;
+ _responseDataStream.SetLength(0);
- using (MemoryStream resMs = new MemoryStream())
- {
- BinaryWriter resWriter = new BinaryWriter(resMs);
+ ServiceCtx context = new ServiceCtx(
+ _context.Device,
+ process,
+ process.CpuMemory,
+ thread,
+ request,
+ response,
+ _requestDataReader,
+ _responseDataWriter);
- ServiceCtx context = new ServiceCtx(
- _context.Device,
- process,
- process.CpuMemory,
- thread,
- request,
- response,
- reqReader,
- resWriter);
+ _sessions[serverSessionHandle].CallHipcMethod(context);
- _sessions[serverSessionHandle].CallHipcMethod(context);
+ response.RawData = _responseDataStream.ToArray();
+ }
+ else if (request.Type == IpcMessageType.HipcControl ||
+ request.Type == IpcMessageType.HipcControlWithContext)
+ {
+ uint magic = (uint)_requestDataReader.ReadUInt64();
+ uint cmdId = (uint)_requestDataReader.ReadUInt64();
- response.RawData = resMs.ToArray();
- }
- }
- else if (request.Type == IpcMessageType.HipcControl ||
- request.Type == IpcMessageType.HipcControlWithContext)
+ switch (cmdId)
{
- uint magic = (uint)reqReader.ReadUInt64();
- uint cmdId = (uint)reqReader.ReadUInt64();
+ case 0:
+ FillHipcResponse(response, 0, _sessions[serverSessionHandle].ConvertToDomain());
+ break;
- switch (cmdId)
- {
- case 0:
- request = FillResponse(response, 0, _sessions[serverSessionHandle].ConvertToDomain());
- break;
-
- case 3:
- request = FillResponse(response, 0, PointerBufferSize);
- break;
+ case 3:
+ FillHipcResponse(response, 0, PointerBufferSize);
+ break;
- // TODO: Whats the difference between IpcDuplicateSession/Ex?
- case 2:
- case 4:
- int unknown = reqReader.ReadInt32();
+ // TODO: Whats the difference between IpcDuplicateSession/Ex?
+ case 2:
+ case 4:
+ int unknown = _requestDataReader.ReadInt32();
- _context.Syscall.CreateSession(out int dupServerSessionHandle, out int dupClientSessionHandle, false, 0);
+ _context.Syscall.CreateSession(out int dupServerSessionHandle, out int dupClientSessionHandle, false, 0);
- AddSessionObj(dupServerSessionHandle, _sessions[serverSessionHandle]);
+ AddSessionObj(dupServerSessionHandle, _sessions[serverSessionHandle]);
- response.HandleDesc = IpcHandleDesc.MakeMove(dupClientSessionHandle);
+ response.HandleDesc = IpcHandleDesc.MakeMove(dupClientSessionHandle);
- request = FillResponse(response, 0);
+ FillHipcResponse(response, 0);
- break;
+ break;
- default: throw new NotImplementedException(cmdId.ToString());
- }
- }
- else if (request.Type == IpcMessageType.HipcCloseSession || request.Type == IpcMessageType.TipcCloseSession)
- {
- _context.Syscall.CloseHandle(serverSessionHandle);
- _sessionHandles.Remove(serverSessionHandle);
- IpcService service = _sessions[serverSessionHandle];
- if (service is IDisposable disposableObj)
- {
- disposableObj.Dispose();
- }
- _sessions.Remove(serverSessionHandle);
- shouldReply = false;
+ default: throw new NotImplementedException(cmdId.ToString());
}
- // If the type is past 0xF, we are using TIPC
- else if (request.Type > IpcMessageType.TipcCloseSession)
- {
- isTipcCommunication = true;
-
- // Response type is always the same as request on TIPC.
- response.Type = request.Type;
+ }
+ else if (request.Type == IpcMessageType.HipcCloseSession || request.Type == IpcMessageType.TipcCloseSession)
+ {
+ _context.Syscall.CloseHandle(serverSessionHandle);
+ _sessionHandles.Remove(serverSessionHandle);
+ IpcService service = _sessions[serverSessionHandle];
+ (service as IDisposable)?.Dispose();
+ _sessions.Remove(serverSessionHandle);
+ shouldReply = false;
+ }
+ // If the type is past 0xF, we are using TIPC
+ else if (request.Type > IpcMessageType.TipcCloseSession)
+ {
+ isTipcCommunication = true;
- using (MemoryStream resMs = new MemoryStream())
- {
- BinaryWriter resWriter = new BinaryWriter(resMs);
+ // Response type is always the same as request on TIPC.
+ response.Type = request.Type;
- ServiceCtx context = new ServiceCtx(
- _context.Device,
- process,
- process.CpuMemory,
- thread,
- request,
- response,
- reqReader,
- resWriter);
+ _responseDataStream.SetLength(0);
- _sessions[serverSessionHandle].CallTipcMethod(context);
+ ServiceCtx context = new ServiceCtx(
+ _context.Device,
+ process,
+ process.CpuMemory,
+ thread,
+ request,
+ response,
+ _requestDataReader,
+ _responseDataWriter);
- response.RawData = resMs.ToArray();
- }
+ _sessions[serverSessionHandle].CallTipcMethod(context);
- process.CpuMemory.Write(messagePtr, response.GetBytesTipc());
- }
- else
- {
- throw new NotImplementedException(request.Type.ToString());
- }
+ response.RawData = _responseDataStream.ToArray();
- if (!isTipcCommunication)
- {
- process.CpuMemory.Write(messagePtr, response.GetBytes((long)messagePtr, recvListAddr | ((ulong)PointerBufferSize << 48)));
- }
+ using var responseStream = response.GetStreamTipc();
+ process.CpuMemory.Write(messagePtr, responseStream.GetReadOnlySequence());
+ }
+ else
+ {
+ throw new NotImplementedException(request.Type.ToString());
+ }
- return shouldReply;
+ if (!isTipcCommunication)
+ {
+ using var responseStream = response.GetStream((long)messagePtr, recvListAddr | ((ulong)PointerBufferSize << 48));
+ process.CpuMemory.Write(messagePtr, responseStream.GetReadOnlySequence());
}
+
+ return shouldReply;
}
- private static IpcMessage FillResponse(IpcMessage response, long result, params int[] values)
+ private static IpcMessage ReadRequest(KProcess process, ulong messagePtr)
{
- using (MemoryStream ms = new MemoryStream())
- {
- BinaryWriter writer = new BinaryWriter(ms);
+ const int messageSize = 0x100;
- foreach (int value in values)
- {
- writer.Write(value);
- }
+ byte[] reqData = ArrayPool<byte>.Shared.Rent(messageSize);
- return FillResponse(response, result, ms.ToArray());
- }
+ Span<byte> reqDataSpan = reqData.AsSpan(0, messageSize);
+ reqDataSpan.Clear();
+
+ process.CpuMemory.Read(messagePtr, reqDataSpan);
+
+ IpcMessage request = new IpcMessage(reqDataSpan, (long)messagePtr);
+
+ ArrayPool<byte>.Shared.Return(reqData);
+
+ return request;
}
- private static IpcMessage FillResponse(IpcMessage response, long result, byte[] data = null)
+ private void FillHipcResponse(IpcMessage response, long result)
{
- response.Type = IpcMessageType.HipcResponse;
+ FillHipcResponse(response, result, ReadOnlySpan<byte>.Empty);
+ }
- using (MemoryStream ms = new MemoryStream())
- {
- BinaryWriter writer = new BinaryWriter(ms);
+ private void FillHipcResponse(IpcMessage response, long result, int value)
+ {
+ Span<byte> span = stackalloc byte[sizeof(int)];
+ BinaryPrimitives.WriteInt32LittleEndian(span, value);
+ FillHipcResponse(response, result, span);
+ }
- writer.Write(IpcMagic.Sfco);
- writer.Write(result);
+ private void FillHipcResponse(IpcMessage response, long result, ReadOnlySpan<byte> data)
+ {
+ response.Type = IpcMessageType.HipcResponse;
- if (data != null)
- {
- writer.Write(data);
- }
+ _responseDataStream.SetLength(0);
- response.RawData = ms.ToArray();
- }
+ _responseDataStream.Write(IpcMagic.Sfco);
+ _responseDataStream.Write(result);
- return response;
+ _responseDataStream.Write(data);
+
+ response.RawData = _responseDataStream.ToArray();
}
protected virtual void Dispose(bool disposing)
@@ -372,6 +385,11 @@ namespace Ryujinx.HLE.HOS.Services
_sessions.Clear();
+ _requestDataReader.Dispose();
+ _requestDataStream.Dispose();
+ _responseDataWriter.Dispose();
+ _responseDataStream.Dispose();
+
InitDone.Dispose();
}
}