Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.
/ corefx Public archive

Add MemoryPool apis #26563

Merged
1 commit merged into from Jan 25, 2018
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
9 changes: 9 additions & 0 deletions src/System.Memory/ref/System.Memory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,15 @@ public partial struct MemoryHandle : System.IDisposable
public unsafe void* Pointer { get { throw null; } }
public void Dispose() { }
}
public abstract class MemoryPool<T> : IDisposable
{
public static System.Buffers.MemoryPool<T> Shared { get; }
public abstract System.Buffers.OwnedMemory<T> Rent(int minBufferSize=-1);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We had a discussion at the API review about the default here. Did we really settle on -1? Why not have the default be 0?

Copy link
Member

@stephentoub stephentoub Jan 24, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not have the default be 0?

FWIW, ArrayPool.Shared.Rent(0) gives back an empty array. Then again, it also throws for -1.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC, we have settled on -1.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's on youtube, it was -1.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you mean "it's on the internet"? :-)

public abstract int MaxBufferSize { get; }
protected MemoryPool() { throw null; }
public void Dispose() { throw null; }
protected abstract void Dispose(bool disposing);
}
public enum OperationStatus
{
DestinationTooSmall = 1,
Expand Down
6 changes: 5 additions & 1 deletion src/System.Memory/src/System.Memory.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@
<Compile Include="System\SpanHelpers.T.cs" />
<Compile Include="System\SpanHelpers.byte.cs" />
<Compile Include="System\ThrowHelper.cs" />
<Compile Include="System\Buffers\ArrayMemoryPool.cs" />
<Compile Include="System\Buffers\ArrayMemoryPool.ArrayMemoryPoolBuffer.cs" />
<Compile Include="System\Buffers\MemoryPool.cs" />
<Compile Include="System\Buffers\OperationStatus.cs" />
<Compile Include="System\Buffers\Binary\Reader.cs" />
<Compile Include="System\Buffers\Binary\ReaderBigEndian.cs" />
Expand Down Expand Up @@ -123,6 +126,7 @@
</Compile>
</ItemGroup>
<ItemGroup Condition="'$(IsPartialFacadeAssembly)' != 'true'">
<Reference Include="System.Buffers" />
<Reference Include="System.Diagnostics.Debug" />
<Reference Include="System.Globalization" />
<Reference Include="System.Reflection" />
Expand All @@ -143,4 +147,4 @@
<ProjectReference Include="..\..\System.Numerics.Vectors\src\System.Numerics.Vectors.csproj" />
</ItemGroup>
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
</Project>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// 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.

using System.Runtime.InteropServices;
#if !netstandard
using Internal.Runtime.CompilerServices;
#else
using System.Runtime.CompilerServices;
#endif

namespace System.Buffers
{
internal sealed partial class ArrayMemoryPool<T> : MemoryPool<T>
{
private sealed class ArrayMemoryPoolBuffer : OwnedMemory<T>
{
private T[] _array;
private int _refCount;

public ArrayMemoryPoolBuffer(int size)
{
_array = ArrayPool<T>.Shared.Rent(size);
}

public sealed override int Length => _array.Length;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do properties and methods on a sealed class need to marked as sealed as well?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do properties and methods on a sealed class need to marked as sealed as well?

No


public sealed override bool IsDisposed => _array == null;

protected sealed override bool IsRetained => _refCount > 0;

public sealed override Span<T> Span
{
get
{
if (IsDisposed)
ThrowHelper.ThrowObjectDisposedException_ArrayMemoryPoolBuffer();

return _array;
}
}

protected sealed override void Dispose(bool disposing)
{
if (_array != null)
{
ArrayPool<T>.Shared.Return(_array);
_array = null;
}
}

protected
#if netstandard // TryGetArray is exposed as "protected internal". Normally, the rules of C# dictate we override it as "protected" because the base class is
// in a different assembly. Except in the netstandard config where the base class is in the same assembly.
internal
#endif
sealed override bool TryGetArray(out ArraySegment<T> arraySegment)
{
if (IsDisposed)
ThrowHelper.ThrowObjectDisposedException_ArrayMemoryPoolBuffer();

arraySegment = new ArraySegment<T>(_array);
return true;
}

public sealed override MemoryHandle Pin(int byteOffset = 0)
{
unsafe
{
Retain(); // this checks IsDisposed

if (byteOffset != 0 && (((uint)byteOffset) - 1) / Unsafe.SizeOf<T>() >= _array.Length)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should allow byteOffset that points right at the end of the array.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(See discussion at #25770 (comment))

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you are right.

ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.byteOffset);

GCHandle handle = GCHandle.Alloc(_array, GCHandleType.Pinned);
return new MemoryHandle(this, ((byte*)handle.AddrOfPinnedObject()) + byteOffset, handle);
}
}

public sealed override void Retain()
{
if (IsDisposed)
ThrowHelper.ThrowObjectDisposedException_ArrayMemoryPoolBuffer();

_refCount++;
}

public sealed override bool Release()
{
if (IsDisposed)
ThrowHelper.ThrowObjectDisposedException_ArrayMemoryPoolBuffer();

int newRefCount = --_refCount;
if (newRefCount < 0)
ThrowHelper.ThrowInvalidOperationException();

return newRefCount != 0;
}
}
}
}
30 changes: 30 additions & 0 deletions src/System.Memory/src/System/Buffers/ArrayMemoryPool.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// 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.

#if !netstandard
using Internal.Runtime.CompilerServices;
#else
using System.Runtime.CompilerServices;
#endif

namespace System.Buffers
{
internal sealed partial class ArrayMemoryPool<T> : MemoryPool<T>
{
private const int s_maxBufferSize = int.MaxValue;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: s_ prefix is used for statics; consts should be PascalCased.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know - this is kinda tough case since the "best" name is already taken by the property. MaxBufferSizeConst just sounds awkward.

public sealed override int MaxBufferSize => s_maxBufferSize;

public sealed override OwnedMemory<T> Rent(int minimumBufferSize = -1)
{
if (minimumBufferSize == -1)
minimumBufferSize = 1 + (4095 / Unsafe.SizeOf<T>());
else if (((uint)minimumBufferSize) > s_maxBufferSize)
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.minimumBufferSize);

return new ArrayMemoryPoolBuffer(minimumBufferSize);
}

protected sealed override void Dispose(bool disposing) {} // ArrayMemoryPool is a shared pool so Dispose() would be a nop even if there were native resources to dispose.
}
}
50 changes: 50 additions & 0 deletions src/System.Memory/src/System/Buffers/MemoryPool.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// 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.

namespace System.Buffers
{
/// <summary>
/// Represents a pool of memory blocks.
/// </summary>
public abstract class MemoryPool<T> : IDisposable
{
private static readonly MemoryPool<T> s_shared = new ArrayMemoryPool<T>();

/// <summary>
/// Returns a singleton instance of a MemoryPool based on arrays.
/// </summary>
public static MemoryPool<T> Shared => s_shared;

/// <summary>
/// Returns a memory block capable of holding at least <paramref name="minBufferSize" /> elements of T.
/// </summary>
/// <param name="minBufferSize">If -1 is passed, this is set to a default value for the pool.</param>
public abstract OwnedMemory<T> Rent(int minBufferSize = -1);

/// <summary>
/// Returns the maximum buffer size supported by this pool.
/// </summary>
public abstract int MaxBufferSize { get; }

/// <summary>
/// Constructs a new instance of a memory pool.
/// </summary>
protected MemoryPool() {}

/// <summary>
/// Frees all resources used by the memory pool.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}

/// <summary>
/// Frees all resources used by the memory pool.
/// </summary>
/// <param name="disposing"></param>
protected abstract void Dispose(bool disposing);
}
}
10 changes: 10 additions & 0 deletions src/System.Memory/src/System/ThrowHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,18 @@ internal static class ThrowHelper
[MethodImpl(MethodImplOptions.NoInlining)]
private static Exception CreateArgumentOutOfRangeException_SymbolDoesNotFit() { return new ArgumentOutOfRangeException("symbol", SR.Argument_BadFormatSpecifier); }

internal static void ThrowInvalidOperationException() { throw CreateInvalidOperationException(); }
[MethodImpl(MethodImplOptions.NoInlining)]
private static Exception CreateInvalidOperationException() { return new InvalidOperationException(); }

internal static void ThrowInvalidOperationException_OutstandingReferences() { throw CreateInvalidOperationException_OutstandingReferences(); }
[MethodImpl(MethodImplOptions.NoInlining)]
private static Exception CreateInvalidOperationException_OutstandingReferences() { return new InvalidOperationException(SR.OutstandingReferences); }

internal static void ThrowObjectDisposedException_ArrayMemoryPoolBuffer() { throw CreateObjectDisposedException_ArrayMemoryPoolBuffer(); }
[MethodImpl(MethodImplOptions.NoInlining)]
private static Exception CreateObjectDisposedException_ArrayMemoryPoolBuffer() { return new ObjectDisposedException("ArrayMemoryPoolBuffer"); }

internal static void ThrowObjectDisposedException_MemoryDisposed() { throw CreateObjectDisposedException_MemoryDisposed(); }
[MethodImpl(MethodImplOptions.NoInlining)]
private static Exception CreateObjectDisposedException_MemoryDisposed() { return new ObjectDisposedException("OwnedMemory<T>", SR.MemoryDisposed); }
Expand Down Expand Up @@ -106,6 +114,8 @@ internal enum ExceptionArgument
text,
obj,
ownedMemory,
minimumBufferSize,
byteOffset,
pointer,
comparable,
comparer
Expand Down
Loading