Add chunk pooling and optimize stack growth

This commit is contained in:
Marek Lemańczyk committed 2025-07-12 03:17:00 +02:00
1 parent 1b4b9f534b
commit ac163014d8
1 file changed
+37 -22
+37 -22
View File
@@ -1,5 +1,7 @@
using System.Collections; using System.Collections;
using System.Numerics; using System.Numerics;
using Microsoft.Extensions.ObjectPool;
using System.Runtime.CompilerServices;
using Recyclable.Collections.Pools; using Recyclable.Collections.Pools;
namespace Recyclable.Collections namespace Recyclable.Collections
@@ -7,23 +9,42 @@ namespace Recyclable.Collections
internal sealed class RecyclableStack<T> : IEnumerable<T>, IDisposable internal sealed class RecyclableStack<T> : IEnumerable<T>, IDisposable
{ {
private static readonly bool _needsClearing = !typeof(T).IsValueType; private static readonly bool _needsClearing = !typeof(T).IsValueType;
private static readonly ObjectPool<Chunk> _chunkPool = new DefaultObjectPool<Chunk>(new DefaultPooledObjectPolicy<Chunk>());
private sealed class Chunk private sealed class Chunk
{ {
internal T[] Buffer; internal T[] Buffer = Array.Empty<T>();
internal int Index; internal int Index;
internal Chunk? Previous; internal Chunk? Previous;
internal Chunk? Next; internal Chunk? Next;
}
internal Chunk(int size, Chunk? previous) [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Chunk RentChunk(int size, Chunk? previous)
{
var chunk = _chunkPool.Get();
chunk.Buffer = size >= RecyclableDefaults.MinPooledArrayLength
? RecyclableArrayPool<T>.RentShared(size)
: new T[size];
chunk.Index = 0;
chunk.Previous = previous;
chunk.Next = null;
return chunk;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void ReturnChunk(Chunk chunk)
{
var buffer = chunk.Buffer;
if (buffer.Length >= RecyclableDefaults.MinPooledArrayLength)
{ {
Buffer = size >= RecyclableDefaults.MinPooledArrayLength RecyclableArrayPool<T>.ReturnShared(buffer, _needsClearing);
? RecyclableArrayPool<T>.RentShared(size)
: new T[size];
Previous = previous;
Next = null;
Index = 0;
} }
chunk.Buffer = Array.Empty<T>();
chunk.Index = 0;
chunk.Previous = null;
chunk.Next = null;
_chunkPool.Return(chunk);
} }
private Chunk _current; private Chunk _current;
@@ -44,7 +65,7 @@ namespace Recyclable.Collections
initialCapacity = (int)BitOperations.RoundUpToPowerOf2((uint)initialCapacity); initialCapacity = (int)BitOperations.RoundUpToPowerOf2((uint)initialCapacity);
} }
_current = new Chunk(initialCapacity, null); _current = RentChunk(initialCapacity, null);
_bottom = _current; _bottom = _current;
_capacity = initialCapacity; _capacity = initialCapacity;
_count = 0; _count = 0;
@@ -128,11 +149,11 @@ namespace Recyclable.Collections
} }
else else
{ {
var doubled = _capacity * 2; var doubled = _capacity << 1;
newSize = (int)Math.Min(doubled - _capacity, RecyclableDefaults.MaxPooledBlockSize); newSize = (int)Math.Min(doubled - _capacity, RecyclableDefaults.MaxPooledBlockSize);
} }
var newChunk = new Chunk(newSize, _current); var newChunk = RentChunk(newSize, _current);
_current.Next = newChunk; _current.Next = newChunk;
_current = newChunk; _current = newChunk;
_capacity += newSize; _capacity += newSize;
@@ -140,18 +161,15 @@ namespace Recyclable.Collections
private void ReleaseCurrentChunk() private void ReleaseCurrentChunk()
{ {
var toReturn = _current.Buffer; var toReturn = _current;
_current = _current.Previous!; _current = toReturn.Previous!;
_current.Next = null; _current.Next = null;
_capacity -= toReturn.Length; _capacity -= toReturn.Buffer.Length;
if (_current.Previous == null) if (_current.Previous == null)
{ {
_bottom = _current; _bottom = _current;
} }
if (toReturn.Length >= RecyclableDefaults.MinPooledArrayLength) ReturnChunk(toReturn);
{
RecyclableArrayPool<T>.ReturnShared(toReturn, _needsClearing);
}
} }
public void Dispose() public void Dispose()
@@ -162,10 +180,7 @@ namespace Recyclable.Collections
} }
Clear(); Clear();
if (_current.Buffer.Length >= RecyclableDefaults.MinPooledArrayLength) ReturnChunk(_current);
{
RecyclableArrayPool<T>.ReturnShared(_current.Buffer, _needsClearing);
}
_disposed = true; _disposed = true;
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
} }