mirror of
https://github.com/Ryujinx/Ryujinx.git
synced 2025-06-28 15:40:47 -07:00
Reducing memory allocations (#4537)
* 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
This commit is contained in:
@ -12,19 +12,5 @@ namespace Ryujinx.Common
|
||||
{
|
||||
return MemoryMarshal.Cast<byte, T>(reader.ReadBytes(Unsafe.SizeOf<T>()))[0];
|
||||
}
|
||||
|
||||
public unsafe static void WriteStruct<T>(this BinaryWriter writer, T value)
|
||||
where T : unmanaged
|
||||
{
|
||||
ReadOnlySpan<byte> data = MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateReadOnlySpan(ref value, 1));
|
||||
|
||||
writer.Write(data);
|
||||
}
|
||||
|
||||
public static void Write(this BinaryWriter writer, UInt128 value)
|
||||
{
|
||||
writer.Write((ulong)value);
|
||||
writer.Write((ulong)(value >> 64));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
28
Ryujinx.Common/Extensions/BinaryWriterExtensions.cs
Normal file
28
Ryujinx.Common/Extensions/BinaryWriterExtensions.cs
Normal file
@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Common
|
||||
{
|
||||
public static class BinaryWriterExtensions
|
||||
{
|
||||
public unsafe static void WriteStruct<T>(this BinaryWriter writer, T value)
|
||||
where T : unmanaged
|
||||
{
|
||||
ReadOnlySpan<byte> data = MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateReadOnlySpan(ref value, 1));
|
||||
|
||||
writer.Write(data);
|
||||
}
|
||||
|
||||
public static void Write(this BinaryWriter writer, UInt128 value)
|
||||
{
|
||||
writer.Write((ulong)value);
|
||||
writer.Write((ulong)(value >> 64));
|
||||
}
|
||||
|
||||
public static void Write(this BinaryWriter writer, MemoryStream stream)
|
||||
{
|
||||
stream.CopyTo(writer.BaseStream);
|
||||
}
|
||||
}
|
||||
}
|
138
Ryujinx.Common/Extensions/StreamExtensions.cs
Normal file
138
Ryujinx.Common/Extensions/StreamExtensions.cs
Normal file
@ -0,0 +1,138 @@
|
||||
using System;
|
||||
using System.Buffers.Binary;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Common
|
||||
{
|
||||
public static class StreamExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Writes a <cref="ReadOnlySpan<int>" /> to this stream.
|
||||
///
|
||||
/// This default implementation converts each buffer value to a stack-allocated
|
||||
/// byte array, then writes it to the Stream using <cref="System.Stream.Write(byte[])" />.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream to be written to</param>
|
||||
/// <param name="buffer">The buffer of values to be written</param>
|
||||
public static void Write(this Stream stream, ReadOnlySpan<int> buffer)
|
||||
{
|
||||
if (buffer.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
ReadOnlySpan<byte> byteBuffer = MemoryMarshal.Cast<int, byte>(buffer);
|
||||
stream.Write(byteBuffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
Span<byte> byteBuffer = stackalloc byte[sizeof(int)];
|
||||
|
||||
foreach (int value in buffer)
|
||||
{
|
||||
BinaryPrimitives.WriteInt32LittleEndian(byteBuffer, value);
|
||||
stream.Write(byteBuffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a four-byte signed integer to this stream. The current position
|
||||
/// of the stream is advanced by four.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream to be written to</param>
|
||||
/// <param name="value">The value to be written</param>
|
||||
public static void Write(this Stream stream, int value)
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[sizeof(int)];
|
||||
BinaryPrimitives.WriteInt32LittleEndian(buffer, value);
|
||||
stream.Write(buffer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes an eight-byte signed integer to this stream. The current position
|
||||
/// of the stream is advanced by eight.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream to be written to</param>
|
||||
/// <param name="value">The value to be written</param>
|
||||
public static void Write(this Stream stream, long value)
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[sizeof(long)];
|
||||
BinaryPrimitives.WriteInt64LittleEndian(buffer, value);
|
||||
stream.Write(buffer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
// Writes a four-byte unsigned integer to this stream. The current position
|
||||
// of the stream is advanced by four.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream to be written to</param>
|
||||
/// <param name="value">The value to be written</param>
|
||||
public static void Write(this Stream stream, uint value)
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[sizeof(uint)];
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(buffer, value);
|
||||
stream.Write(buffer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes an eight-byte unsigned integer to this stream. The current
|
||||
/// position of the stream is advanced by eight.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream to be written to</param>
|
||||
/// <param name="value">The value to be written</param>
|
||||
public static void Write(this Stream stream, ulong value)
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[sizeof(ulong)];
|
||||
BinaryPrimitives.WriteUInt64LittleEndian(buffer, value);
|
||||
stream.Write(buffer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the contents of source to stream by calling source.CopyTo(stream).
|
||||
/// Provides consistency with other Stream.Write methods.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream to be written to</param>
|
||||
/// <param name="source">The stream to be read from</param>
|
||||
public static void Write(this Stream stream, Stream source)
|
||||
{
|
||||
source.CopyTo(stream);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a sequence of bytes to the Stream.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream to be written to.</param>
|
||||
/// <param name="value">The byte to be written</param>
|
||||
/// <param name="count">The number of times the value should be written</param>
|
||||
public static void WriteByte(this Stream stream, byte value, int count)
|
||||
{
|
||||
if (count <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const int BlockSize = 16;
|
||||
|
||||
int blockCount = count / BlockSize;
|
||||
if (blockCount > 0)
|
||||
{
|
||||
Span<byte> span = stackalloc byte[BlockSize];
|
||||
span.Fill(value);
|
||||
for (int x = 0; x < blockCount; x++)
|
||||
{
|
||||
stream.Write(span);
|
||||
}
|
||||
}
|
||||
|
||||
int nonBlockBytes = count % BlockSize;
|
||||
for (int x = 0; x < nonBlockBytes; x++)
|
||||
{
|
||||
stream.WriteByte(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
99
Ryujinx.Common/Memory/MemoryStreamManager.cs
Normal file
99
Ryujinx.Common/Memory/MemoryStreamManager.cs
Normal file
@ -0,0 +1,99 @@
|
||||
using Microsoft.IO;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Common.Memory
|
||||
{
|
||||
public static class MemoryStreamManager
|
||||
{
|
||||
private static readonly RecyclableMemoryStreamManager _shared = new RecyclableMemoryStreamManager();
|
||||
|
||||
/// <summary>
|
||||
/// We don't expose the <c>RecyclableMemoryStreamManager</c> directly because version 2.x
|
||||
/// returns them as <c>MemoryStream</c>. This Shared class is here to a) offer only the GetStream() versions we use
|
||||
/// and b) return them as <c>RecyclableMemoryStream</c> so we don't have to cast.
|
||||
/// </summary>
|
||||
public static class Shared
|
||||
{
|
||||
/// <summary>
|
||||
/// Retrieve a new <c>MemoryStream</c> object with no tag and a default initial capacity.
|
||||
/// </summary>
|
||||
/// <returns>A <c>RecyclableMemoryStream</c></returns>
|
||||
public static RecyclableMemoryStream GetStream()
|
||||
=> new RecyclableMemoryStream(_shared);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve a new <c>MemoryStream</c> object with the contents copied from the provided
|
||||
/// buffer. The provided buffer is not wrapped or used after construction.
|
||||
/// </summary>
|
||||
/// <remarks>The new stream's position is set to the beginning of the stream when returned.</remarks>
|
||||
/// <param name="buffer">The byte buffer to copy data from</param>
|
||||
/// <returns>A <c>RecyclableMemoryStream</c></returns>
|
||||
public static RecyclableMemoryStream GetStream(byte[] buffer)
|
||||
=> GetStream(Guid.NewGuid(), null, buffer, 0, buffer.Length);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve a new <c>MemoryStream</c> object with the given tag and with contents copied from the provided
|
||||
/// buffer. The provided buffer is not wrapped or used after construction.
|
||||
/// </summary>
|
||||
/// <remarks>The new stream's position is set to the beginning of the stream when returned.</remarks>
|
||||
/// <param name="buffer">The byte buffer to copy data from</param>
|
||||
/// <returns>A <c>RecyclableMemoryStream</c></returns>
|
||||
public static RecyclableMemoryStream GetStream(ReadOnlySpan<byte> buffer)
|
||||
=> GetStream(Guid.NewGuid(), null, buffer);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve a new <c>RecyclableMemoryStream</c> object with the given tag and with contents copied from the provided
|
||||
/// buffer. The provided buffer is not wrapped or used after construction.
|
||||
/// </summary>
|
||||
/// <remarks>The new stream's position is set to the beginning of the stream when returned.</remarks>
|
||||
/// <param name="id">A unique identifier which can be used to trace usages of the stream</param>
|
||||
/// <param name="tag">A tag which can be used to track the source of the stream</param>
|
||||
/// <param name="buffer">The byte buffer to copy data from</param>
|
||||
/// <returns>A <c>RecyclableMemoryStream</c></returns>
|
||||
public static RecyclableMemoryStream GetStream(Guid id, string tag, ReadOnlySpan<byte> buffer)
|
||||
{
|
||||
RecyclableMemoryStream stream = null;
|
||||
try
|
||||
{
|
||||
stream = new RecyclableMemoryStream(_shared, id, tag, buffer.Length);
|
||||
stream.Write(buffer);
|
||||
stream.Position = 0;
|
||||
return stream;
|
||||
}
|
||||
catch
|
||||
{
|
||||
stream?.Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve a new <c>RecyclableMemoryStream</c> object with the given tag and with contents copied from the provided
|
||||
/// buffer. The provided buffer is not wrapped or used after construction.
|
||||
/// </summary>
|
||||
/// <remarks>The new stream's position is set to the beginning of the stream when returned</remarks>
|
||||
/// <param name="id">A unique identifier which can be used to trace usages of the stream</param>
|
||||
/// <param name="tag">A tag which can be used to track the source of the stream</param>
|
||||
/// <param name="buffer">The byte buffer to copy data from</param>
|
||||
/// <param name="offset">The offset from the start of the buffer to copy from</param>
|
||||
/// <param name="count">The number of bytes to copy from the buffer</param>
|
||||
/// <returns>A <c>RecyclableMemoryStream</c></returns>
|
||||
public static RecyclableMemoryStream GetStream(Guid id, string tag, byte[] buffer, int offset, int count)
|
||||
{
|
||||
RecyclableMemoryStream stream = null;
|
||||
try
|
||||
{
|
||||
stream = new RecyclableMemoryStream(_shared, id, tag, count);
|
||||
stream.Write(buffer, offset, count);
|
||||
stream.Position = 0;
|
||||
return stream;
|
||||
}
|
||||
catch
|
||||
{
|
||||
stream?.Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -7,6 +7,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" />
|
||||
<PackageReference Include="MsgPack.Cli" />
|
||||
<PackageReference Include="System.Management" />
|
||||
</ItemGroup>
|
||||
|
@ -1,3 +1,5 @@
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@ -38,12 +40,7 @@ namespace Ryujinx.Common
|
||||
return null;
|
||||
}
|
||||
|
||||
using (var mem = new MemoryStream())
|
||||
{
|
||||
stream.CopyTo(mem);
|
||||
|
||||
return mem.ToArray();
|
||||
}
|
||||
return StreamUtils.StreamToBytes(stream);
|
||||
}
|
||||
}
|
||||
|
||||
@ -56,12 +53,7 @@ namespace Ryujinx.Common
|
||||
return null;
|
||||
}
|
||||
|
||||
using (var mem = new MemoryStream())
|
||||
{
|
||||
await stream.CopyToAsync(mem);
|
||||
|
||||
return mem.ToArray();
|
||||
}
|
||||
return await StreamUtils.StreamToBytesAsync(stream);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,8 @@
|
||||
using System.IO;
|
||||
using Microsoft.IO;
|
||||
using Ryujinx.Common.Memory;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ryujinx.Common.Utilities
|
||||
{
|
||||
@ -6,12 +10,22 @@ namespace Ryujinx.Common.Utilities
|
||||
{
|
||||
public static byte[] StreamToBytes(Stream input)
|
||||
{
|
||||
using (MemoryStream stream = new MemoryStream())
|
||||
using (MemoryStream stream = MemoryStreamManager.Shared.GetStream())
|
||||
{
|
||||
input.CopyTo(stream);
|
||||
|
||||
return stream.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<byte[]> StreamToBytesAsync(Stream input, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using (MemoryStream stream = MemoryStreamManager.Shared.GetStream())
|
||||
{
|
||||
await input.CopyToAsync(stream, cancellationToken);
|
||||
|
||||
return stream.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user