Merge pull request #7 from mlemanczyk/codex/implementuj-klase-recyclablequeue

Implement queue using pooled chunks
This commit is contained in:
Marek Lemańczyk authored and GitHub committed 2025-07-14 00:30:50 +02:00
commit 6f9ffd7916
3 files changed
+277 -174

No files matched your search

@@ -0,0 +1,20 @@
namespace Recyclable.Collections.Pools
{
public sealed class BiDirectionalRecyclableArrayPoolChunk<T>
{
private static readonly T[] _empty = Array.Empty<T>();
internal static T[] Empty => _empty;
internal T[] Buffer;
internal int Top;
internal int Bottom;
internal BiDirectionalRecyclableArrayPoolChunk<T>? Previous;
internal BiDirectionalRecyclableArrayPoolChunk<T>? Next;
public BiDirectionalRecyclableArrayPoolChunk()
{
Buffer = _empty;
}
}
}
@@ -0,0 +1,37 @@
using Microsoft.Extensions.ObjectPool;
namespace Recyclable.Collections.Pools
{
public static class BiDirectionalRecyclableArrayPoolChunkPool<T>
{
private static readonly ObjectPool<BiDirectionalRecyclableArrayPoolChunk<T>> _pool =
new DefaultObjectPool<BiDirectionalRecyclableArrayPoolChunk<T>>(new DefaultPooledObjectPolicy<BiDirectionalRecyclableArrayPoolChunk<T>>());
public static BiDirectionalRecyclableArrayPoolChunk<T> Rent() => _pool.Get();
public static void Return(BiDirectionalRecyclableArrayPoolChunk<T> chunk)
{
chunk.Top = 0;
chunk.Bottom = 0;
chunk.Previous = null;
chunk.Next = null;
_pool.Return(chunk);
}
public static void Dispose(BiDirectionalRecyclableArrayPoolChunk<T> chunk, bool needsClearing)
{
var buffer = chunk.Buffer;
if (buffer.Length >= RecyclableDefaults.MinPooledArrayLength)
{
RecyclableArrayPool<T>.ReturnShared(buffer, needsClearing);
}
chunk.Buffer = BiDirectionalRecyclableArrayPoolChunk<T>.Empty;
chunk.Top = 0;
chunk.Bottom = 0;
chunk.Previous = null;
chunk.Next = null;
_pool.Return(chunk);
}
}
}
+220 -174
View File
@@ -1,211 +1,257 @@
using System.Buffers;
using System.Collections;
using System.Numerics;
using System.Runtime.CompilerServices;
using Recyclable.Collections.Pools;
namespace Recyclable.Collections
{
internal class RecyclableQueue<T> : IRecyclableOwner<T>, IList<T>, IDisposable
{
private static readonly ArrayPool<T> _arrayPool = ArrayPool<T>.Create();
private static readonly IEqualityComparer<T> _equalityComparer = EqualityComparer<T>.Default;
internal sealed class RecyclableQueue<T> : IEnumerable<T>, IDisposable
{
private static readonly bool _needsClearing = !typeof(T).IsValueType;
private bool _disposedValue;
private RecyclableList<T[]> Memory { get; }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static BiDirectionalRecyclableArrayPoolChunk<T> RentChunk(int size, BiDirectionalRecyclableArrayPoolChunk<T>? previous)
{
var chunk = BiDirectionalRecyclableArrayPoolChunkPool<T>.Rent();
if (chunk.Buffer.Length < size)
{
if (chunk.Buffer.Length >= RecyclableDefaults.MinPooledArrayLength)
{
RecyclableArrayPool<T>.ReturnShared(chunk.Buffer, _needsClearing);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static T Dequeue(RecyclableQueue<T> queue)
{
if (queue.LongCount <= 0)
{
throw new ArgumentOutOfRangeException(nameof(queue), "There are no items in the queue");
}
chunk.Buffer = size >= RecyclableDefaults.MinPooledArrayLength
? RecyclableArrayPool<T>.RentShared(size)
: new T[size];
}
var toRemove = queue[0];
queue.RemovedCount++;
queue.LongCount--;
if (queue.Capacity - queue.LongCount == queue.BlockSize)
{
queue.RemoveBlock(0);
queue.RemovedCount -= queue.BlockSize;
}
chunk.Top = 0;
chunk.Bottom = 0;
chunk.Previous = previous;
chunk.Next = null;
return chunk;
}
return toRemove;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void ReturnChunk(BiDirectionalRecyclableArrayPoolChunk<T> chunk)
{
BiDirectionalRecyclableArrayPoolChunkPool<T>.Return(chunk);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool TryDequeue(RecyclableQueue<T> queue, out T? item)
{
if (queue.LongCount > 0)
{
item = queue.Dequeue();
return true;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void DisposeChunk(BiDirectionalRecyclableArrayPoolChunk<T> chunk)
{
BiDirectionalRecyclableArrayPoolChunkPool<T>.Dispose(chunk, _needsClearing);
}
item = default;
return false;
}
private BiDirectionalRecyclableArrayPoolChunk<T> _head;
private BiDirectionalRecyclableArrayPoolChunk<T> _tail;
private bool _disposed;
private long _capacity;
private long _count;
protected static readonly bool NeedsClearing = !typeof(T).IsValueType;
public RecyclableQueue(int initialCapacity = RecyclableDefaults.InitialCapacity)
{
if (initialCapacity < 1)
{
initialCapacity = 1;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected static long GetAbsoluteIndex(long index, long removedCount) => index + removedCount;
if (!BitOperations.IsPow2((uint)initialCapacity))
{
initialCapacity = (int)BitOperations.RoundUpToPowerOf2((uint)initialCapacity);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected long GetRelativeIndex(long index) => index - RemovedCount;
_head = RentChunk(initialCapacity, null);
_tail = _head;
_capacity = initialCapacity;
_count = 0;
}
protected long RemovedCount { get; set; }
public RecyclableQueue(IEnumerable<T> source, int initialCapacity = RecyclableDefaults.InitialCapacity) : this(initialCapacity)
{
foreach (var item in source)
{
Enqueue(item);
}
}
public RecyclableQueue(IEnumerable<T> source, int blockSize = RecyclableDefaults.BlockSize)
{
BlockSize = blockSize;
Memory = new(1);
foreach (var item in source)
{
Enqueue(item);
}
}
public int Count => checked((int)_count);
public long LongCount => _count;
public RecyclableQueue(int blockSize = RecyclableDefaults.BlockSize)
{
BlockSize = blockSize;
Memory = new(1);
}
public void Enqueue(T item)
{
if (_tail.Top == _tail.Buffer.Length)
{
Grow();
}
public int BlockSize { get; protected set; }
public long Capacity { get; set; }
_tail.Buffer[_tail.Top++] = item;
_count++;
}
public int Count
{
get => checked((int)LongCount);
set => LongCount = value;
}
public void Add(T item) => Enqueue(item);
public long LongCount { get; set; }
public bool IsReadOnly { get; }
public T Dequeue()
{
if (_count == 0)
{
ThrowHelper.ThrowArgumentOutOfRangeException(nameof(_count), "Queue is empty");
}
public T this[int index]
{
get => GetItem(index);
set => SetItem(index, value);
}
_count--;
var value = _head.Buffer[_head.Bottom];
if (_needsClearing)
{
_head.Buffer[_head.Bottom] = default!;
}
public T this[long index]
{
get => GetItem(index);
set => SetItem(index, value);
}
_head.Bottom++;
if (_head.Bottom == _head.Top && _head.Next != null)
{
ReleaseHeadChunk();
}
else if (_head.Bottom == _head.Top)
{
_head.Bottom = 0;
_head.Top = 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void SetItem(long index, T value)
{
long absoluteIndex = GetAbsoluteIndex(index, RemovedCount);
int arrayIndex = RecyclableQueueHelpers<T>.ToArrayIndex(absoluteIndex, BlockSize);
long itemIndex = RecyclableQueueHelpers<T>.ToItemIndex(absoluteIndex, BlockSize);
Memory[arrayIndex][itemIndex] = value;
}
return value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private T GetItem(long index)
{
long absoluteIndex = GetAbsoluteIndex(index, RemovedCount);
int arrayIndex = RecyclableQueueHelpers<T>.ToArrayIndex(absoluteIndex, BlockSize);
long itemIndex = RecyclableQueueHelpers<T>.ToItemIndex(absoluteIndex, BlockSize);
return Memory[arrayIndex][itemIndex];
}
public bool TryDequeue(out T? item)
{
if (_count > 0)
{
item = Dequeue();
return true;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Enqueue(T item)
{
if (LongCount == Capacity)
{
AddBlock();
}
item = default;
return false;
}
SetItem(LongCount, item);
LongCount++;
}
public void Clear()
{
while (_head.Next != null)
{
ReleaseHeadChunk();
}
public void AddBlock()
{
T[] newArray = (BlockSize >= RecyclableDefaults.MinPooledArrayLength)
? _arrayPool.Rent(BlockSize)
: new T[BlockSize];
if (_needsClearing && _head.Top > _head.Bottom)
{
Array.Clear(_head.Buffer, _head.Bottom, _head.Top - _head.Bottom);
}
Memory.Add(newArray);
Capacity += BlockSize;
}
_capacity = _head.Buffer.Length;
_head.Top = 0;
_head.Bottom = 0;
_tail = _head;
_count = 0;
}
public T Dequeue() => Dequeue(this);
public Enumerator GetEnumerator() => new(this);
IEnumerator<T> IEnumerable<T>.GetEnumerator() => new Enumerator(this);
IEnumerator IEnumerable.GetEnumerator() => new Enumerator(this);
public void Add(T item) => Enqueue(item);
private void Grow()
{
int newSize;
if (_capacity >= RecyclableDefaults.MaxPooledBlockSize)
{
newSize = RecyclableDefaults.MaxPooledBlockSize;
}
else
{
var doubled = _capacity << 1;
newSize = (int)Math.Min(doubled - _capacity, RecyclableDefaults.MaxPooledBlockSize);
}
public void Clear()
{
try
{
// Remove in reversed order for performance savings
while (Memory._count > 0)
{
try
{
RemoveBlock(Memory._count - 1);
}
#pragma warning disable RCS1075 // We want to try returning as many arrays, as possible, before the list is cleared.
catch (Exception)
#pragma warning restore RCS1075
{
}
}
}
finally
{
LongCount = 0;
RemovedCount = 0;
Memory.Clear();
}
}
var newChunk = RentChunk(newSize, _tail);
_tail.Next = newChunk;
_tail = newChunk;
_capacity += newSize;
}
#pragma warning disable CS0618
public bool Contains(T item) => RecyclableQueueHelpers<T>.Contains(Memory, item);
public void CopyTo(T[] array, int arrayIndex) => RecyclableQueueHelpers<T>.CopyTo(Memory, RemovedCount, BlockSize, checked((int)(LongCount % BlockSize)), array, arrayIndex);
public IEnumerable<T> Enumerate() => RecyclableQueueHelpers<T>.Enumerate(Memory, BlockSize, LongCount);
public IEnumerator<T> GetEnumerator() => RecyclableQueueHelpers<T>.Enumerate(Memory, BlockSize, LongCount).GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => RecyclableQueueHelpers<T>.Enumerate(Memory, BlockSize, LongCount).GetEnumerator();
public int IndexOf(T item) => checked((int)GetRelativeIndex(RecyclableQueueHelpers<T>.LongIndexOf(Memory, BlockSize, item, _equalityComparer)));
public long LongIndexOf(T item) => GetRelativeIndex(RecyclableQueueHelpers<T>.LongIndexOf(Memory, BlockSize, item, _equalityComparer));
#pragma warning restore CS0618
private void ReleaseHeadChunk()
{
var toReturn = _head;
_head = toReturn.Next!;
_head.Previous = null;
_capacity -= toReturn.Buffer.Length;
ReturnChunk(toReturn);
}
public void Insert(int index, T item) => throw new NotSupportedException();
public void RemoveAt(int index) => throw new NotSupportedException();
public bool TryDequeue(out T? item) => TryDequeue(this, out item);
public bool Remove(T item) => TryDequeue(out var _);
public void RemoveBlock(int index)
{
try
{
if (Memory[index].LongLength is > RecyclableDefaults.MinPooledArrayLength and <= int.MaxValue)
{
_arrayPool.Return(Memory[index], NeedsClearing);
}
public void Dispose()
{
if (_disposed)
{
return;
}
Memory.RemoveAt(index);
}
finally
{
Capacity -= BlockSize;
//LongCount -= BlockSize;
}
}
var chunk = _head;
while (chunk != null)
{
var next = chunk.Next;
DisposeChunk(chunk);
chunk = next;
}
public void Dispose()
{
if (!_disposedValue)
{
Clear();
_disposedValue = true;
}
_disposed = true;
GC.SuppressFinalize(this);
}
GC.SuppressFinalize(this);
}
}
public struct Enumerator : IEnumerator<T>
{
private readonly BiDirectionalRecyclableArrayPoolChunk<T>? _start;
private BiDirectionalRecyclableArrayPoolChunk<T>? _chunk;
private int _index;
private T? _current;
internal Enumerator(RecyclableQueue<T> queue)
{
_start = queue._head;
_chunk = queue._head;
_index = _chunk != null ? _chunk.Bottom : 0;
_current = default;
}
public T Current => _current!;
object IEnumerator.Current => _current!;
public bool MoveNext()
{
if (_chunk == null)
{
return false;
}
if (_index >= _chunk.Top)
{
_chunk = _chunk.Next;
if (_chunk == null)
{
return false;
}
_index = _chunk.Bottom;
}
_current = _chunk.Buffer[_index++];
return true;
}
public void Reset()
{
_chunk = _start;
_index = _chunk != null ? _chunk.Bottom : 0;
}
public void Dispose()
{
_chunk = null;
_current = default;
}
}
}
}