Skip to content
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

[API Proposal]: using support for ArrayPool #112839

Open
huoyaoyuan opened this issue Feb 24, 2025 · 7 comments
Open

[API Proposal]: using support for ArrayPool #112839

huoyaoyuan opened this issue Feb 24, 2025 · 7 comments
Labels
api-suggestion Early API idea and discussion, it is NOT ready for implementation area-System.Buffers untriaged New issue has not been triaged by the area owner

Comments

@huoyaoyuan
Copy link
Member

Background and motivation

Currently, ArrayPool<T>.Shared is used more widely in user code and BCL code, especially async methods. Typical use cases look like:

byte[] buffer = ArrayPool<byte>.Shared.Rent(bufferLength);
try
{
    ConsumeSync(buffer.AsSpan());
    await ConsumeAsync(buffer.AsMemory());
}
finally
{
    ArrayPool<byte>.Shared.Return(buffer);
}

The try-finally pattern currently can't leverage using syntax to simplify the code. In contrast, MemoryPool<T> is providing using support through IMemoryOwner<T>:

using IMemoryOwner<byte> buffer = MemoryPool<byte>.Shared.Rent(bufferLength);
{
    ConsomeSync(buffer.Memory.Span);
    await ConsumeAsync(buffer.Memory);
}

However, MemoryPool<T> and IMemoryOwner<T> aren't suitable for many use cases of ArrayPool<T> because of the higher overhead of IMemoryOwner<T> and Memory<T>. Thus, it can be worthy to add using support for ArrayPool<T> for low overhead.

API Proposal

namespace System.Buffers;

public abstract partial class ArrayPool<T>
{
    // existing
    public abstract T[] Rent(int minimumLength);
    public abstract void Return(T[] array, bool clearArray = false);
    // new
+   public Lease RentLease(int minimumLength, bool clearOnReturn = false);
+   public struct Lease : IDisposable
+   {
+       public T[] Array { get; }
+       public void Dispose();
+   }
}

API Usage

using (var buffer = ArrayPool<T>.Shared.Rent(bufferLength))
{
    ConsumeSync(buffer.Array.AsSpan());
    await ConsumeAsync(buffer.Array.AsMemory());
}

Alternative Designs

Making RentLease virtual. Unlikely because it would create either additional performance penalty on top of Rent, or duplicated code with Rent if overridden.

Risks

When Lease is captured in async method, it will capture additional fields comparing to raw array management. The pool (usually ArrayPool<T>.Shared and additional arguments for return (clearOnReturn) need to be captured.

@huoyaoyuan huoyaoyuan added api-suggestion Early API idea and discussion, it is NOT ready for implementation area-System.Buffers labels Feb 24, 2025
@dotnet-policy-service dotnet-policy-service bot added the untriaged New issue has not been triaged by the area owner label Feb 24, 2025
Copy link
Contributor

Tagging subscribers to this area: @dotnet/area-system-buffers
See info in area-owners.md if you want to be subscribed.

@EgorBo
Copy link
Member

EgorBo commented Feb 24, 2025

Typical use cases look like:

The typical use should not involve try-finally as it makes it fragile and vulnerable to potential use-after-free bugs (unless it is 100% predictable who/how can throw an exception)

The end goal is to hide 95% of usages with something like #52065

@stephentoub
Copy link
Member

stephentoub commented Feb 24, 2025

A struct like this also makes it even more likely to accidentally return the buffer multiple times. And using a class instead is exactly what MemoryPool provides.

With all of the deabstraction work being done by @AndyAyersMS, I would also hope that this allocation could end up frequently being implicitly stack allocated, once all the right inlining and the like happens.

@EgorBo
Copy link
Member

EgorBo commented Feb 24, 2025

With all of the deabstraction work being done by @AndyAyersMS, I would also hope that this allocation could end up frequently being implicitly stack allocated, once all the right inlining and the like happens.

Presumably, it is unlikely JIT can ever do "stackalloc or ArrayPool" under the hood for a normal allocation, only "stackalloc or heap", so an explicit API probably still good idea.
Unless we're fine with "stackalloc or heap" and the possible GC overhead

@stephentoub
Copy link
Member

Presumably, it is unlikely JIT can ever do "stackalloc or ArrayPool" under the hood for a normal allocation, only "stackalloc or heap", so an explicit API probably still good idea.

I wasn't talking about the array allocation. Rather, I was talking about the IDisposable object wrapper it allocates for the returned array.

@huoyaoyuan
Copy link
Member Author

I would also hope that this allocation could end up frequently being implicitly stack allocated

Should this cover synchronous cases only? What about the async cases?

@stephentoub
Copy link
Member

I would also hope that this allocation could end up frequently being implicitly stack allocated

Should this cover synchronous cases only? What about the async cases?

It would hopefully cover the async cases as well in the 99% case once the new async support lands in the runtime.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api-suggestion Early API idea and discussion, it is NOT ready for implementation area-System.Buffers untriaged New issue has not been triaged by the area owner
Projects
None yet
Development

No branches or pull requests

3 participants