/// <summary>Takes an array from the bucket. If the bucket is empty, returns null.</summary> internal T[]? Rent() { T[]?[] buffers = _buffers; T[]? buffer = null; // While holding the lock, grab whatever is at the next available index and // update the index. We do as little work as possible while holding the spin // lock to minimize contention with other threads. The try/finally is // necessary to properly handle thread aborts on platforms which have them. bool lockTaken = false, allocateBuffer = false; try { _lock.Enter(ref lockTaken); if (_index < buffers.Length) { buffer = buffers[_index]; buffers[_index++] = null; allocateBuffer = buffer == null; } } finally { if (lockTaken) { _lock.Exit(false); } } // While we were holding the lock, we grabbed whatever was at the next available index, if // there was one. If we tried and if we got back null, that means we hadn't yet allocated // for that slot, in which case we should do so now. if (allocateBuffer) { buffer = new T[_bufferLength]; ArrayPoolEventSource log = ArrayPoolEventSource.Log; if (log.IsEnabled()) { log.BufferAllocated(buffer.GetHashCode(), _bufferLength, _poolId, Id, ArrayPoolEventSource.BufferAllocatedReason.Pooled); } } return(buffer); }
public override T[] Rent(int minimumLength) { // Arrays can't be smaller than zero. We allow requesting zero-length arrays (even though // pooling such an array isn't valuable) as it's a valid length array, and we want the pool // to be usable in general instead of using `new`, even for computed lengths. if (minimumLength < 0) { throw new ArgumentOutOfRangeException(nameof(minimumLength)); } else if (minimumLength == 0) { // No need to log the empty array. Our pool is effectively infinite // and we'll never allocate for rents and never store for returns. return(Array.Empty <T>()); } ArrayPoolEventSource log = ArrayPoolEventSource.Log; T[]? buffer; // Get the bucket number for the array length int bucketIndex = Utilities.SelectBucketIndex(minimumLength); // If the array could come from a bucket... if (bucketIndex < _buckets.Length) { // First try to get it from TLS if possible. T[]?[]? tlsBuckets = t_tlsBuckets; if (tlsBuckets != null) { buffer = tlsBuckets[bucketIndex]; if (buffer != null) { tlsBuckets[bucketIndex] = null; if (log.IsEnabled()) { log.BufferRented(buffer.GetHashCode(), buffer.Length, Id, bucketIndex); } return(buffer); } } // We couldn't get a buffer from TLS, so try the global stack. PerCoreLockedStacks?b = _buckets[bucketIndex]; if (b != null) { buffer = b.TryPop(); if (buffer != null) { if (log.IsEnabled()) { log.BufferRented(buffer.GetHashCode(), buffer.Length, Id, bucketIndex); } return(buffer); } } // No buffer available. Allocate a new buffer with a size corresponding to the appropriate bucket. buffer = GC.AllocateUninitializedArray <T>(_bucketArraySizes[bucketIndex]); } 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 = GC.AllocateUninitializedArray <T>(minimumLength); } if (log.IsEnabled()) { int bufferId = buffer.GetHashCode(), bucketId = -1; // no bucket for an on-demand allocated buffer log.BufferRented(bufferId, buffer.Length, Id, bucketId); log.BufferAllocated(bufferId, buffer.Length, Id, bucketId, bucketIndex >= _buckets.Length ? ArrayPoolEventSource.BufferAllocatedReason.OverMaximumSize : ArrayPoolEventSource.BufferAllocatedReason.PoolExhausted); } return(buffer); }
public override T[] Rent(int minimumLength) { ArrayPoolEventSource log = ArrayPoolEventSource.Log; T[]? buffer; // Get the bucket number for the array length. The result may be out of range of buckets, // either for too large a value or for 0 and negative values. int bucketIndex = Utilities.SelectBucketIndex(minimumLength); // First, try to get an array from TLS if possible. ThreadLocalArray[]? tlsBuckets = t_tlsBuckets; if (tlsBuckets is not null && (uint)bucketIndex < (uint)tlsBuckets.Length) { buffer = tlsBuckets[bucketIndex].Array; if (buffer is not null) { tlsBuckets[bucketIndex].Array = null; if (log.IsEnabled()) { log.BufferRented(buffer.GetHashCode(), buffer.Length, Id, bucketIndex); } return(buffer); } } // Next, try to get an array from one of the per-core stacks. PerCoreLockedStacks?[] perCoreBuckets = _buckets; if ((uint)bucketIndex < (uint)perCoreBuckets.Length) { PerCoreLockedStacks?b = perCoreBuckets[bucketIndex]; if (b is not null) { buffer = b.TryPop(); if (buffer is not null) { if (log.IsEnabled()) { log.BufferRented(buffer.GetHashCode(), buffer.Length, Id, bucketIndex); } return(buffer); } } // No buffer available. Ensure the length we'll allocate matches that of a bucket // so we can later return it. minimumLength = Utilities.GetMaxSizeForBucket(bucketIndex); } else if (minimumLength == 0) { // We allow requesting zero-length arrays (even though pooling such an array isn't valuable) // as it's a valid length array, and we want the pool to be usable in general instead of using // `new`, even for computed lengths. But, there's no need to log the empty array. Our pool is // effectively infinite for empty arrays and we'll never allocate for rents and never store for returns. return(Array.Empty <T>()); } else if (minimumLength < 0) { throw new ArgumentOutOfRangeException(nameof(minimumLength)); } buffer = GC.AllocateUninitializedArray <T>(minimumLength); if (log.IsEnabled()) { int bufferId = buffer.GetHashCode(); log.BufferRented(bufferId, buffer.Length, Id, ArrayPoolEventSource.NoBucketId); log.BufferAllocated(bufferId, buffer.Length, Id, ArrayPoolEventSource.NoBucketId, bucketIndex >= _buckets.Length ? ArrayPoolEventSource.BufferAllocatedReason.OverMaximumSize : ArrayPoolEventSource.BufferAllocatedReason.PoolExhausted); } return(buffer); }
public override T[] Rent(int minimumLength) { // Arrays can't be smaller than zero. We allow requesting zero-length arrays (even though // pooling such an array isn't valuable) as it's a valid length array, and we want the pool // to be usable in general instead of using `new`, even for computed lengths. if (minimumLength < 0) { throw new ArgumentOutOfRangeException(nameof(minimumLength)); } else if (minimumLength == 0) { // No need for events with the empty array. Our pool is effectively infinite // and we'll never allocate for rents and never store for returns. return(Array.Empty <T>()); } ArrayPoolEventSource log = ArrayPoolEventSource.Log; T[]? buffer; 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]; } if (log.IsEnabled()) { int bufferId = buffer.GetHashCode(), bucketId = -1; // no bucket for an on-demand allocated buffer log.BufferRented(bufferId, buffer.Length, Id, bucketId); log.BufferAllocated(bufferId, buffer.Length, Id, bucketId, index >= _buckets.Length ? ArrayPoolEventSource.BufferAllocatedReason.OverMaximumSize : ArrayPoolEventSource.BufferAllocatedReason.PoolExhausted); } return(buffer); }