MemoryPool<byte>.Rent(int minBufferSize ) 能否返回比要求的更大的 IMemoryOwner<byte>?

Can MemoryPool<byte>.Rent(int minBufferSize ) capable of returning a IMemoryOwner<byte> bigger than asked?

我知道 IMemoryOwner<byte>.Memory 使用的内部缓冲区可能比要求的要大。但是, IMemoryOwner<byte>.Memory.Length 是用我问的还是用内部缓冲区的大小定义的?文档好像不够准确。

一起来看看

MemoryPool<T>.Rent is abstract, so we'll go looking for an implementation. ArrayMemoryPool<T>.Rent 看起来是个不错的有代表性的候选人。该实现如下所示:

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

    return new ArrayMemoryPoolBuffer(minimumBufferSize);
}

让我们把它追到 ArrayMemoryPoolBuffer:

private sealed class ArrayMemoryPoolBuffer : IMemoryOwner<T>
{
    private T[]? _array;

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

    public Memory<T> Memory
    {
        get
        {
            T[]? array = _array;
            if (array == null)
            {
                ThrowHelper.ThrowObjectDisposedException_ArrayMemoryPoolBuffer();
            }

            return new Memory<T>(array);
        }
    }

    public void Dispose()
    {
        T[]? array = _array;
        if (array != null)
        {
            _array = null;
            ArrayPool<T>.Shared.Return(array);
        }
    }
}

new Memory<T>(array) 意味着 Memory<T>.Length 将只是底层数组的大小:我们没有任何逻辑可以让较小的 Memory<T> 包裹较大的数组。那么让我们看看 ArrayPool<T>.Shared.Rent 是否返回一个正确大小的数组...

又是abstract, but we can find an implementation at ConfigurableArrayPool<T>。该方法的核心是:

int index = Utilities.SelectBucketIndex(minimumLength);
if (index < _buckets.Length)
{
    // Search for an array starting at the 'index' bucket. If the bucket is empty, bump up to the
    // next higher bucket and try that one, but only try at most a few buckets.
    const int MaxBucketsToTry = 2;
    int i = index;
    do
    {
        // Attempt to rent from the bucket.  If we get a buffer from it, return it.
        buffer = _buckets[i].Rent();
        if (buffer != null)
        {
            if (log.IsEnabled())
            {
                log.BufferRented(buffer.GetHashCode(), buffer.Length, Id, _buckets[i].Id);
            }
            return buffer;
        }
    }
    while (++i < _buckets.Length && i != index + MaxBucketsToTry);

    // The pool was exhausted for this buffer size.  Allocate a new buffer with a size corresponding
    // to the appropriate bucket.
    buffer = new T[_buckets[index]._bufferLength];
}
else
{
    // The request was for a size too large for the pool.  Allocate an array of exactly the requested length.
    // When it's returned to the pool, we'll simply throw it away.
    buffer = new T[minimumLength];
}

我们可以看到池中有多个桶,每个桶包含指定大小的数组。此方法正在查找刚好大于请求大小的数组桶;如果那个桶是空的,它会寻找更大的桶。如果找不到任何东西,它会创建一个新数组,其大小为 bucket size;只有当请求的大小大于池可以管理的大小时,它才会创建一个精确请求大小的数组。

所以 Memory<T> 下的数组不太可能具有请求的大小,并且 Memory<T> 的构造不会假装 Memory<T> 小于其下层数组数组。

结论是IMemoryOwner<byte>.Memory.Length确实可以比请求的大