Use pooled memory and avoid memory copies (#6691)

* perf: use ByteMemoryPool

* feat: KPageTableBase/KPageTable new methods to read and write `ReadOnlySequence<byte>`

* new: add IWritableBlock.Write(ulong, ReadOnlySequence<byte>) with default impl

* perf: use GetReadOnlySequence() instead of GetSpan()

* perf: make `Parcel` IDisposable, use `ByteMemoryPool` for internal allocation, and make Parcel consumers dispose of it

* remove comment about copySize

* remove unnecessary Clear()
This commit is contained in:
jhorv 2024-04-21 06:57:35 -04:00 committed by GitHub
parent 7070cf6ae5
commit 216026c096
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 98 additions and 25 deletions

View file

@ -1,8 +1,10 @@
using Ryujinx.Audio.Backends.Common; using Ryujinx.Audio.Backends.Common;
using Ryujinx.Audio.Common; using Ryujinx.Audio.Common;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Common.Memory;
using Ryujinx.Memory; using Ryujinx.Memory;
using System; using System;
using System.Buffers;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Threading; using System.Threading;
@ -87,7 +89,9 @@ namespace Ryujinx.Audio.Backends.SDL2
return; return;
} }
byte[] samples = new byte[frameCount * _bytesPerFrame]; using IMemoryOwner<byte> samplesOwner = ByteMemoryPool.Rent(frameCount * _bytesPerFrame);
Span<byte> samples = samplesOwner.Memory.Span;
_ringBuffer.Read(samples, 0, samples.Length); _ringBuffer.Read(samples, 0, samples.Length);

View file

@ -1,8 +1,10 @@
using Ryujinx.Audio.Backends.Common; using Ryujinx.Audio.Backends.Common;
using Ryujinx.Audio.Backends.SoundIo.Native; using Ryujinx.Audio.Backends.SoundIo.Native;
using Ryujinx.Audio.Common; using Ryujinx.Audio.Common;
using Ryujinx.Common.Memory;
using Ryujinx.Memory; using Ryujinx.Memory;
using System; using System;
using System.Buffers;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Threading; using System.Threading;
@ -37,7 +39,7 @@ namespace Ryujinx.Audio.Backends.SoundIo
_outputStream = _driver.OpenStream(RequestedSampleFormat, RequestedSampleRate, RequestedChannelCount); _outputStream = _driver.OpenStream(RequestedSampleFormat, RequestedSampleRate, RequestedChannelCount);
_outputStream.WriteCallback += Update; _outputStream.WriteCallback += Update;
_outputStream.Volume = requestedVolume; _outputStream.Volume = requestedVolume;
// TODO: Setup other callbacks (errors, ect). // TODO: Setup other callbacks (errors, etc.)
_outputStream.Open(); _outputStream.Open();
} }
@ -120,7 +122,9 @@ namespace Ryujinx.Audio.Backends.SoundIo
int channelCount = areas.Length; int channelCount = areas.Length;
byte[] samples = new byte[frameCount * bytesPerFrame]; using IMemoryOwner<byte> samplesOwner = ByteMemoryPool.Rent(frameCount * bytesPerFrame);
Span<byte> samples = samplesOwner.Memory.Span;
_ringBuffer.Read(samples, 0, samples.Length); _ringBuffer.Read(samples, 0, samples.Length);

View file

@ -570,7 +570,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
} }
else else
{ {
serverProcess.CpuMemory.Write(copyDst, clientProcess.CpuMemory.GetSpan(copySrc, (int)copySize)); serverProcess.CpuMemory.Write(copyDst, clientProcess.CpuMemory.GetReadOnlySequence(copySrc, (int)copySize));
} }
if (clientResult != Result.Success) if (clientResult != Result.Success)
@ -858,7 +858,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
} }
else else
{ {
clientProcess.CpuMemory.Write(copyDst, serverProcess.CpuMemory.GetSpan(copySrc, (int)copySize)); clientProcess.CpuMemory.Write(copyDst, serverProcess.CpuMemory.GetReadOnlySequence(copySrc, (int)copySize));
} }
} }

View file

@ -2,6 +2,7 @@ using Ryujinx.Horizon.Common;
using Ryujinx.Memory; using Ryujinx.Memory;
using Ryujinx.Memory.Range; using Ryujinx.Memory.Range;
using System; using System;
using System.Buffers;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
@ -34,6 +35,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
} }
} }
/// <inheritdoc/>
protected override ReadOnlySequence<byte> GetReadOnlySequence(ulong va, int size)
{
return _cpuMemory.GetReadOnlySequence(va, size);
}
/// <inheritdoc/> /// <inheritdoc/>
protected override ReadOnlySpan<byte> GetSpan(ulong va, int size) protected override ReadOnlySpan<byte> GetSpan(ulong va, int size)
{ {
@ -247,6 +254,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
_cpuMemory.SignalMemoryTracking(va, size, write); _cpuMemory.SignalMemoryTracking(va, size, write);
} }
/// <inheritdoc/>
protected override void Write(ulong va, ReadOnlySequence<byte> data)
{
_cpuMemory.Write(va, data);
}
/// <inheritdoc/> /// <inheritdoc/>
protected override void Write(ulong va, ReadOnlySpan<byte> data) protected override void Write(ulong va, ReadOnlySpan<byte> data)
{ {

View file

@ -5,6 +5,7 @@ using Ryujinx.Horizon.Common;
using Ryujinx.Memory; using Ryujinx.Memory;
using Ryujinx.Memory.Range; using Ryujinx.Memory.Range;
using System; using System;
using System.Buffers;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
@ -1568,7 +1569,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
while (size > 0) while (size > 0)
{ {
ulong copySize = 0x100000; // Copy chunck size. Any value will do, moderate sizes are recommended. ulong copySize = int.MaxValue;
if (copySize > size) if (copySize > size)
{ {
@ -1577,11 +1578,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
if (toServer) if (toServer)
{ {
currentProcess.CpuMemory.Write(serverAddress, GetSpan(clientAddress, (int)copySize)); currentProcess.CpuMemory.Write(serverAddress, GetReadOnlySequence(clientAddress, (int)copySize));
} }
else else
{ {
Write(clientAddress, currentProcess.CpuMemory.GetSpan(serverAddress, (int)copySize)); Write(clientAddress, currentProcess.CpuMemory.GetReadOnlySequence(serverAddress, (int)copySize));
} }
serverAddress += copySize; serverAddress += copySize;
@ -1911,9 +1912,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
Context.Memory.Fill(GetDramAddressFromPa(dstFirstPagePa), unusedSizeBefore, (byte)_ipcFillValue); Context.Memory.Fill(GetDramAddressFromPa(dstFirstPagePa), unusedSizeBefore, (byte)_ipcFillValue);
ulong copySize = addressRounded <= endAddr ? addressRounded - address : size; ulong copySize = addressRounded <= endAddr ? addressRounded - address : size;
var data = srcPageTable.GetSpan(addressTruncated + unusedSizeBefore, (int)copySize); var data = srcPageTable.GetReadOnlySequence(addressTruncated + unusedSizeBefore, (int)copySize);
Context.Memory.Write(GetDramAddressFromPa(dstFirstPagePa + unusedSizeBefore), data); ((IWritableBlock)Context.Memory).Write(GetDramAddressFromPa(dstFirstPagePa + unusedSizeBefore), data);
firstPageFillAddress += unusedSizeBefore + copySize; firstPageFillAddress += unusedSizeBefore + copySize;
@ -1977,9 +1978,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
if (send) if (send)
{ {
ulong copySize = endAddr - endAddrTruncated; ulong copySize = endAddr - endAddrTruncated;
var data = srcPageTable.GetSpan(endAddrTruncated, (int)copySize); var data = srcPageTable.GetReadOnlySequence(endAddrTruncated, (int)copySize);
Context.Memory.Write(GetDramAddressFromPa(dstLastPagePa), data); ((IWritableBlock)Context.Memory).Write(GetDramAddressFromPa(dstLastPagePa), data);
lastPageFillAddr += copySize; lastPageFillAddr += copySize;
@ -2943,6 +2944,18 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
/// <param name="pageList">Page list where the ranges will be added</param> /// <param name="pageList">Page list where the ranges will be added</param>
protected abstract void GetPhysicalRegions(ulong va, ulong size, KPageList pageList); protected abstract void GetPhysicalRegions(ulong va, ulong size, KPageList pageList);
/// <summary>
/// Gets a read-only sequence of data from CPU mapped memory.
/// </summary>
/// <remarks>
/// Allows reading non-contiguous memory without first copying it to a newly allocated single contiguous block.
/// </remarks>
/// <param name="va">Virtual address of the data</param>
/// <param name="size">Size of the data</param>
/// <returns>A read-only sequence of the data</returns>
/// <exception cref="Ryujinx.Memory.InvalidMemoryRegionException">Throw for unhandled invalid or unmapped memory accesses</exception>
protected abstract ReadOnlySequence<byte> GetReadOnlySequence(ulong va, int size);
/// <summary> /// <summary>
/// Gets a read-only span of data from CPU mapped memory. /// Gets a read-only span of data from CPU mapped memory.
/// </summary> /// </summary>
@ -2952,7 +2965,6 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
/// </remarks> /// </remarks>
/// <param name="va">Virtual address of the data</param> /// <param name="va">Virtual address of the data</param>
/// <param name="size">Size of the data</param> /// <param name="size">Size of the data</param>
/// <param name="tracked">True if read tracking is triggered on the span</param>
/// <returns>A read-only span of the data</returns> /// <returns>A read-only span of the data</returns>
/// <exception cref="Ryujinx.Memory.InvalidMemoryRegionException">Throw for unhandled invalid or unmapped memory accesses</exception> /// <exception cref="Ryujinx.Memory.InvalidMemoryRegionException">Throw for unhandled invalid or unmapped memory accesses</exception>
protected abstract ReadOnlySpan<byte> GetSpan(ulong va, int size); protected abstract ReadOnlySpan<byte> GetSpan(ulong va, int size);
@ -3060,6 +3072,14 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
/// <param name="size">Size of the region</param> /// <param name="size">Size of the region</param>
protected abstract void SignalMemoryTracking(ulong va, ulong size, bool write); protected abstract void SignalMemoryTracking(ulong va, ulong size, bool write);
/// <summary>
/// Writes data to CPU mapped memory, with write tracking.
/// </summary>
/// <param name="va">Virtual address to write the data into</param>
/// <param name="data">Data to be written</param>
/// <exception cref="Ryujinx.Memory.InvalidMemoryRegionException">Throw for unhandled invalid or unmapped memory accesses</exception>
protected abstract void Write(ulong va, ReadOnlySequence<byte> data);
/// <summary> /// <summary>
/// Writes data to CPU mapped memory, with write tracking. /// Writes data to CPU mapped memory, with write tracking.
/// </summary> /// </summary>

View file

@ -13,10 +13,10 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
ResultCode OnTransact(uint code, uint flags, ReadOnlySpan<byte> inputParcel, Span<byte> outputParcel) ResultCode OnTransact(uint code, uint flags, ReadOnlySpan<byte> inputParcel, Span<byte> outputParcel)
{ {
Parcel inputParcelReader = new(inputParcel.ToArray()); using Parcel inputParcelReader = new(inputParcel);
// TODO: support objects? // TODO: support objects?
Parcel outputParcelWriter = new((uint)(outputParcel.Length - Unsafe.SizeOf<ParcelHeader>()), 0); using Parcel outputParcelWriter = new((uint)(outputParcel.Length - Unsafe.SizeOf<ParcelHeader>()), 0);
string inputInterfaceToken = inputParcelReader.ReadInterfaceToken(); string inputInterfaceToken = inputParcelReader.ReadInterfaceToken();

View file

@ -1,7 +1,9 @@
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Common.Memory;
using Ryujinx.Common.Utilities; using Ryujinx.Common.Utilities;
using Ryujinx.HLE.HOS.Services.SurfaceFlinger.Types; using Ryujinx.HLE.HOS.Services.SurfaceFlinger.Types;
using System; using System;
using System.Buffers;
using System.Diagnostics; using System.Diagnostics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@ -9,13 +11,13 @@ using System.Text;
namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
{ {
class Parcel sealed class Parcel : IDisposable
{ {
private readonly byte[] _rawData; private readonly IMemoryOwner<byte> _rawDataOwner;
private Span<byte> Raw => new(_rawData); private Span<byte> Raw => _rawDataOwner.Memory.Span;
private ref ParcelHeader Header => ref MemoryMarshal.Cast<byte, ParcelHeader>(_rawData)[0]; private ref ParcelHeader Header => ref MemoryMarshal.Cast<byte, ParcelHeader>(Raw)[0];
private Span<byte> Payload => Raw.Slice((int)Header.PayloadOffset, (int)Header.PayloadSize); private Span<byte> Payload => Raw.Slice((int)Header.PayloadOffset, (int)Header.PayloadSize);
@ -24,9 +26,11 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
private int _payloadPosition; private int _payloadPosition;
private int _objectPosition; private int _objectPosition;
public Parcel(byte[] rawData) private bool _isDisposed;
public Parcel(ReadOnlySpan<byte> data)
{ {
_rawData = rawData; _rawDataOwner = ByteMemoryPool.RentCopy(data);
_payloadPosition = 0; _payloadPosition = 0;
_objectPosition = 0; _objectPosition = 0;
@ -36,7 +40,7 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
{ {
uint headerSize = (uint)Unsafe.SizeOf<ParcelHeader>(); uint headerSize = (uint)Unsafe.SizeOf<ParcelHeader>();
_rawData = new byte[BitUtils.AlignUp<uint>(headerSize + payloadSize + objectsSize, 4)]; _rawDataOwner = ByteMemoryPool.RentCleared(BitUtils.AlignUp<uint>(headerSize + payloadSize + objectsSize, 4));
Header.PayloadSize = payloadSize; Header.PayloadSize = payloadSize;
Header.ObjectsSize = objectsSize; Header.ObjectsSize = objectsSize;
@ -132,7 +136,9 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
// TODO: figure out what this value is // TODO: figure out what this value is
WriteInplaceObject(new byte[4] { 0, 0, 0, 0 }); Span<byte> fourBytes = stackalloc byte[4];
WriteInplaceObject(fourBytes);
} }
public AndroidStrongPointer<T> ReadStrongPointer<T>() where T : unmanaged, IFlattenable public AndroidStrongPointer<T> ReadStrongPointer<T>() where T : unmanaged, IFlattenable
@ -219,5 +225,15 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
return Raw[..(int)(Header.PayloadSize + Header.ObjectsSize + Unsafe.SizeOf<ParcelHeader>())]; return Raw[..(int)(Header.PayloadSize + Header.ObjectsSize + Unsafe.SizeOf<ParcelHeader>())];
} }
public void Dispose()
{
if (!_isDisposed)
{
_isDisposed = true;
_rawDataOwner.Dispose();
}
}
} }
} }

View file

@ -250,7 +250,7 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService
context.Device.System.SurfaceFlinger.SetRenderLayer(layerId); context.Device.System.SurfaceFlinger.SetRenderLayer(layerId);
Parcel parcel = new(0x28, 0x4); using Parcel parcel = new(0x28, 0x4);
parcel.WriteObject(producer, "dispdrv\0"); parcel.WriteObject(producer, "dispdrv\0");
@ -288,7 +288,7 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService
context.Device.System.SurfaceFlinger.SetRenderLayer(layerId); context.Device.System.SurfaceFlinger.SetRenderLayer(layerId);
Parcel parcel = new(0x28, 0x4); using Parcel parcel = new(0x28, 0x4);
parcel.WriteObject(producer, "dispdrv\0"); parcel.WriteObject(producer, "dispdrv\0");

View file

@ -1,9 +1,25 @@
using System; using System;
using System.Buffers;
namespace Ryujinx.Memory namespace Ryujinx.Memory
{ {
public interface IWritableBlock public interface IWritableBlock
{ {
/// <summary>
/// Writes data to CPU mapped memory, with write tracking.
/// </summary>
/// <param name="va">Virtual address to write the data into</param>
/// <param name="data">Data to be written</param>
/// <exception cref="InvalidMemoryRegionException">Throw for unhandled invalid or unmapped memory accesses</exception>
void Write(ulong va, ReadOnlySequence<byte> data)
{
foreach (ReadOnlyMemory<byte> segment in data)
{
Write(va, segment.Span);
va += (ulong)segment.Length;
}
}
void Write(ulong va, ReadOnlySpan<byte> data); void Write(ulong va, ReadOnlySpan<byte> data);
void WriteUntracked(ulong va, ReadOnlySpan<byte> data) => Write(va, data); void WriteUntracked(ulong va, ReadOnlySpan<byte> data) => Write(va, data);