Skip to content

Performance improvement in MemoryStream #3477

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Buffers;
using System.Diagnostics.Contracts;
using System.IO;
using System.Runtime.CompilerServices;
using Microsoft.Toolkit.HighPerformance.Streams;
using MemoryStream = Microsoft.Toolkit.HighPerformance.Streams.MemoryStream;

namespace Microsoft.Toolkit.HighPerformance.Extensions
{
Expand All @@ -18,17 +19,18 @@ public static class IMemoryOwnerExtensions
/// <summary>
/// Returns a <see cref="Stream"/> wrapping the contents of the given <see cref="IMemoryOwner{T}"/> of <see cref="byte"/> instance.
/// </summary>
/// <param name="memory">The input <see cref="IMemoryOwner{T}"/> of <see cref="byte"/> instance.</param>
/// <returns>A <see cref="Stream"/> wrapping the data within <paramref name="memory"/>.</returns>
/// <param name="memoryOwner">The input <see cref="IMemoryOwner{T}"/> of <see cref="byte"/> instance.</param>
/// <returns>A <see cref="Stream"/> wrapping the data within <paramref name="memoryOwner"/>.</returns>
/// <remarks>
/// The caller does not need to track the lifetime of the input <see cref="IMemoryOwner{T}"/> of <see cref="byte"/>
/// instance, as the returned <see cref="Stream"/> will take care of disposing that buffer when it is closed.
/// </remarks>
/// <exception cref="ArgumentException">Thrown when <paramref name="memoryOwner"/> has an invalid data store.</exception>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Stream AsStream(this IMemoryOwner<byte> memory)
public static Stream AsStream(this IMemoryOwner<byte> memoryOwner)
{
return new IMemoryOwnerStream(memory);
return MemoryStream.Create(memoryOwner);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,12 @@ public static class MemoryExtensions
/// In particular, the caller must ensure that the target buffer is not disposed as long
/// as the returned <see cref="Stream"/> is in use, to avoid unexpected issues.
/// </remarks>
/// <exception cref="ArgumentException">Thrown when <paramref name="memory"/> has an invalid data store.</exception>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Stream AsStream(this Memory<byte> memory)
{
return new MemoryStream(memory);
return MemoryStream.Create(memory, false);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using System;
using System.Diagnostics.Contracts;
using System.IO;
using System.Runtime.CompilerServices;
using MemoryStream = Microsoft.Toolkit.HighPerformance.Streams.MemoryStream;

namespace Microsoft.Toolkit.HighPerformance.Extensions
Expand All @@ -26,11 +25,11 @@ public static class ReadOnlyMemoryExtensions
/// In particular, the caller must ensure that the target buffer is not disposed as long
/// as the returned <see cref="Stream"/> is in use, to avoid unexpected issues.
/// </remarks>
/// <exception cref="ArgumentException">Thrown when <paramref name="memory"/> has an invalid data store.</exception>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Stream AsStream(this ReadOnlyMemory<byte> memory)
{
return new MemoryStream(memory);
return MemoryStream.Create(memory, true);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Buffers;
using System.IO;

Expand All @@ -10,29 +11,32 @@ namespace Microsoft.Toolkit.HighPerformance.Streams
/// <summary>
/// A <see cref="Stream"/> implementation wrapping an <see cref="IMemoryOwner{T}"/> of <see cref="byte"/> instance.
/// </summary>
internal sealed class IMemoryOwnerStream : MemoryStream
/// <typeparam name="TSource">The type of source to use for the underlying data.</typeparam>
internal sealed class IMemoryOwnerStream<TSource> : MemoryStream<TSource>
where TSource : struct, ISpanOwner
{
/// <summary>
/// The <see cref="IMemoryOwner{T}"/> of <see cref="byte"/> instance currently in use.
/// The <see cref="IDisposable"/> instance currently in use.
/// </summary>
private readonly IMemoryOwner<byte> memory;
private readonly IDisposable disposable;

/// <summary>
/// Initializes a new instance of the <see cref="IMemoryOwnerStream"/> class.
/// Initializes a new instance of the <see cref="IMemoryOwnerStream{TSource}"/> class.
/// </summary>
/// <param name="memory">The input <see cref="IMemoryOwner{T}"/> of <see cref="byte"/> instance to use.</param>
public IMemoryOwnerStream(IMemoryOwner<byte> memory)
: base(memory.Memory)
/// <param name="source">The input <typeparamref name="TSource"/> instance to use.</param>
/// <param name="disposable">The <see cref="IDisposable"/> instance currently in use.</param>
public IMemoryOwnerStream(TSource source, IDisposable disposable)
: base(source, false)
{
this.memory = memory;
this.disposable = disposable;
}

/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);

this.memory.Dispose();
this.disposable.Dispose();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Licensed to the .NET Foundation under one or more agreements.
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

Expand All @@ -8,16 +8,41 @@
namespace Microsoft.Toolkit.HighPerformance.Streams
{
/// <summary>
/// A <see cref="Stream"/> implementation wrapping a <see cref="Memory{T}"/> or <see cref="ReadOnlyMemory{T}"/> instance.
/// A factory class to produce <see cref="MemoryStream{TSource}"/> instances.
/// </summary>
internal partial class MemoryStream
internal static partial class MemoryStream
{
/// <summary>
/// Throws an <see cref="ArgumentException"/> when trying to write too many bytes to the target stream.
/// </summary>
public static void ThrowArgumentExceptionForEndOfStreamOnWrite()
{
throw new ArgumentException("The current stream can't contain the requested input data.");
}

/// <summary>
/// Throws a <see cref="NotSupportedException"/> when trying to set the length of the stream.
/// </summary>
public static void ThrowNotSupportedExceptionForSetLength()
{
throw new NotSupportedException("Setting the length is not supported for this stream.");
}

/// <summary>
/// Throws an <see cref="ArgumentException"/> when using an invalid seek mode.
/// </summary>
/// <returns>Nothing, as this method throws unconditionally.</returns>
public static long ThrowArgumentExceptionForSeekOrigin()
{
throw new ArgumentException("The input seek mode is not valid.", "origin");
}

/// <summary>
/// Throws an <see cref="ArgumentOutOfRangeException"/> when setting the <see cref="Stream.Position"/> property.
/// </summary>
private static void ThrowArgumentOutOfRangeExceptionForPosition()
{
throw new ArgumentOutOfRangeException(nameof(Position), "The value for the property was not in the valid range.");
throw new ArgumentOutOfRangeException(nameof(Stream.Position), "The value for the property was not in the valid range.");
}

/// <summary>
Expand Down Expand Up @@ -60,37 +85,12 @@ private static void ThrowNotSupportedExceptionForCanWrite()
throw new NotSupportedException("The current stream doesn't support writing.");
}

/// <summary>
/// Throws an <see cref="ArgumentException"/> when trying to write too many bytes to the target stream.
/// </summary>
private static void ThrowArgumentExceptionForEndOfStreamOnWrite()
{
throw new ArgumentException("The current stream can't contain the requested input data.");
}

/// <summary>
/// Throws a <see cref="NotSupportedException"/> when trying to set the length of the stream.
/// </summary>
private static void ThrowNotSupportedExceptionForSetLength()
{
throw new NotSupportedException("Setting the length is not supported for this stream.");
}

/// <summary>
/// Throws an <see cref="ArgumentException"/> when using an invalid seek mode.
/// </summary>
/// <returns>Nothing, as this method throws unconditionally.</returns>
private static long ThrowArgumentExceptionForSeekOrigin()
{
throw new ArgumentException("The input seek mode is not valid.", "origin");
}

/// <summary>
/// Throws an <see cref="ObjectDisposedException"/> when using a disposed <see cref="Stream"/> instance.
/// </summary>
private static void ThrowObjectDisposedException()
{
throw new ObjectDisposedException(nameof(memory), "The current stream has already been disposed");
throw new ObjectDisposedException("source", "The current stream has already been disposed");
}
}
}
22 changes: 11 additions & 11 deletions Microsoft.Toolkit.HighPerformance/Streams/MemoryStream.Validate.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Licensed to the .NET Foundation under one or more agreements.
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

Expand All @@ -8,17 +8,17 @@
namespace Microsoft.Toolkit.HighPerformance.Streams
{
/// <summary>
/// A <see cref="Stream"/> implementation wrapping a <see cref="System.Memory{T}"/> or <see cref="System.ReadOnlyMemory{T}"/> instance.
/// A factory class to produce <see cref="MemoryStream{TSource}"/> instances.
/// </summary>
internal partial class MemoryStream
internal static partial class MemoryStream
{
/// <summary>
/// Validates the <see cref="Stream.Position"/> argument.
/// </summary>
/// <param name="position">The new <see cref="Stream.Position"/> value being set.</param>
/// <param name="length">The maximum length of the target <see cref="Stream"/>.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void ValidatePosition(long position, int length)
public static void ValidatePosition(long position, int length)
{
if ((ulong)position >= (ulong)length)
{
Expand All @@ -33,7 +33,7 @@ private static void ValidatePosition(long position, int length)
/// <param name="offset">The offset within <paramref name="buffer"/>.</param>
/// <param name="count">The number of elements to process within <paramref name="buffer"/>.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void ValidateBuffer(byte[]? buffer, int offset, int count)
public static void ValidateBuffer(byte[]? buffer, int offset, int count)
{
if (buffer is null)
{
Expand All @@ -57,24 +57,24 @@ private static void ValidateBuffer(byte[]? buffer, int offset, int count)
}

/// <summary>
/// Validates the <see cref="CanWrite"/> property.
/// Validates the <see cref="MemoryStream{TSource}.CanWrite"/> property.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ValidateCanWrite()
public static void ValidateCanWrite(bool canWrite)
{
if (!CanWrite)
if (!canWrite)
{
ThrowNotSupportedExceptionForCanWrite();
}
}

/// <summary>
/// Validates that the current instance hasn't been disposed.
/// Validates that a given <see cref="Stream"/> instance hasn't been disposed.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ValidateDisposed()
public static void ValidateDisposed(bool disposed)
{
if (this.disposed)
if (disposed)
{
ThrowObjectDisposedException();
}
Expand Down
Loading