Performance & standardization improvements.

Business logic fixes.
RecyclableQueue & RecyclableStack added - pending improvements.
This commit is contained in:
mlemanczyk committed 2023-01-06 13:54:32 +01:00
1 parent 4e5835b20e
commit aba28028b1
17 files changed
+1269 -190

No files matched your search

@@ -0,0 +1,10 @@
namespace Recyclable.Collections
{
public interface IRecyclableOwner<T> : IList<T>
{
T this[long index] { get; set; }
int BlockSize { get; }
long Capacity { get; set; }
long LongCount { get; set; }
}
}
+4 -9
View File
@@ -81,13 +81,10 @@ namespace Recyclable.Collections
while (stack.TryPop(out range));
}
public static void QuickSort<T>(this IList<T> values, IComparer<T> comparer, IRandomNumberGenerator randomNumberGenerator)
{
values.QuickSort(0, values.Count - 1, comparer, randomNumberGenerator);
}
=> values.QuickSort(0, values.Count - 1, comparer, randomNumberGenerator);
public static void QuickSort<T>(this IList<T> values)
{
values.QuickSort(Comparer<T>.Default, new SystemRandomNumberGenerator());
}
=> values.QuickSort(Comparer<T>.Default, new SystemRandomNumberGenerator());
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static T SampleMedian<T>(this IList<T> values, int startIndex, int endIndex, IComparer<T> comparer, IRandomNumberGenerator randomNumberGenerator)
@@ -116,8 +113,6 @@ namespace Recyclable.Collections
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void Swap<T>(this IList<T> values, int xIndex, int yIndex)
{
(values[yIndex], values[xIndex]) = (values[xIndex], values[yIndex]);
}
=> (values[yIndex], values[xIndex]) = (values[xIndex], values[yIndex]);
}
}
+56 -162
View File
@@ -1,132 +1,81 @@
using System.Buffers;
using System.Collections;
using System.Collections;
using System.Runtime.CompilerServices;
namespace Recyclable.Collections
{
public class RecyclableList<T> : IDisposable, IList<T>
{
private static readonly IEqualityComparer<T> _comparer = EqualityComparer<T>.Default;
private static readonly IEqualityComparer<T> _equalityComparer = EqualityComparer<T>.Default;
private readonly int _blockSize;
private readonly List<T[]> _arrays = new();
private bool _disposedValue;
private long _capacity;
protected uint _isUpdating;
private static IEnumerable<T> EnumerateElements(List<T[]> arrays, int chunkSize, long totalCount)
{
long currentCount = 0;
for (var arrayIdx = 0; arrayIdx < arrays.Count; arrayIdx++)
{
var array = arrays[arrayIdx];
currentCount += chunkSize;
switch (currentCount < totalCount)
{
case true:
for (var valueIdx = 0; valueIdx < chunkSize; valueIdx++)
{
yield return array[valueIdx];
}
break;
case false:
var partialCount = (int)(totalCount % chunkSize);
int maxCount = partialCount > 0 ? partialCount : chunkSize;
for (var valueIdx = 0; valueIdx < maxCount; valueIdx++)
{
yield return array[valueIdx];
}
break;
}
}
}
private static T[] RentArray(int minSize) => ArrayPool<T>.Shared.Rent(minSize);
private static void ReturnArray(in T[] array) => ArrayPool<T>.Shared.Return(array);
public RecyclableList(int blockSize = RecyclableDefaults.BlockSize)
{
_blockSize = blockSize;
BlockSize = blockSize;
}
public RecyclableList(IEnumerable<T> source, int blockSize = RecyclableDefaults.BlockSize)
{
_blockSize = blockSize;
BeginUpdate();
try
BlockSize = blockSize;
foreach (var item in source)
{
foreach (var item in source)
{
Add(item);
}
}
finally
{
EndUpdate();
Add(item);
}
}
public int BlockSize { get; }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SetItem(List<T[]> arrays, in int blockSize, in long index, in T value)
=> arrays[(int)(index / blockSize)][index % blockSize] = value;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T GetItem(List<T[]> arrays, in int blockSize, in long index)
=> arrays[(int)(index / blockSize)][index % blockSize];
public T this[long index]
{
get => _arrays[(int)(index / _blockSize)][index % _blockSize];
set
{
_arrays[(int)(index / _blockSize)][index % _blockSize] = value;
if (!IsUpdating)
{
ListUpdated();
}
}
get => GetItem(_arrays, BlockSize, index);
set => SetItem(_arrays, BlockSize, index, value);
}
public T this[int index]
{
get => this[(long)index];
set => this[(long)index] = value;
get => GetItem(_arrays, BlockSize, index);
set => SetItem(_arrays, BlockSize, index, value);
}
public int Count => LongCount <= int.MaxValue ? (int)LongCount : int.MaxValue;
public long LongCount { get; protected set; }
public long Capacity { get; set; }
public int Count => (int)LongCount;
public long LongCount { get; set; }
public bool IsReadOnly { get; } = false;
public bool IsUpdating => _isUpdating > 0;
public int BlockCount => _arrays.Count;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Add(T item)
{
if (LongCount == _capacity)
if (LongCount == Capacity)
{
var newArray = RentArray(_blockSize);
T[] newArray = BlockSize.RentArrayFromPool<T>();
_arrays.Add(newArray);
_capacity += _blockSize;
Capacity += BlockSize;
}
var newIndex = LongCount;
// We don't want this[newIndex] set to raise the update event, because
// the count would be wrong. We need to increase it, first.
BeginUpdate();
try
{
this[newIndex] = item;
LongCount++;
}
finally
{
EndUpdate();
}
this[LongCount] = item;
LongCount++;
}
public void Clear()
{
BeginUpdate();
try
{
for (var arrayIdx = 0; arrayIdx < _arrays.Count; arrayIdx++)
// Remove in reversed order for performance savings
while (_arrays.Count > 0)
{
try
{
ReturnArray(_arrays[arrayIdx]);
RemoveBlock(_arrays.Count - 1);
}
catch (Exception)
{
@@ -137,81 +86,30 @@ namespace Recyclable.Collections
}
finally
{
try
{
// Make sure the list is cleared so that we no longer use any of the arrays
LongCount = 0;
_arrays.Clear();
}
finally
{
EndUpdate();
}
LongCount = 0;
_arrays.Clear();
}
}
public bool Contains(T item) => _arrays.Any(x => x.Contains(item));
public bool Contains(T item) => _arrays.Contains(item);
public void CopyTo(T[] array, int arrayIndex)
{
Span<T> arrayMemory = array.AsSpan()[arrayIndex..];
for (var arrayIdx = 0; arrayIdx < _arrays.Count - 1; arrayIdx++)
{
T[] partialArray = _arrays[arrayIdx];
ReadOnlySpan<T> sourceMemory = partialArray.AsSpan();
sourceMemory = sourceMemory[.._blockSize];
sourceMemory.CopyTo(arrayMemory);
arrayMemory = arrayMemory[_blockSize..];
}
public void CopyTo(T[] array, int arrayIndex) => _arrays.CopyTo(0, BlockSize, (int)LongCount % BlockSize, array, arrayIndex);
if (_arrays.Count > 0)
{
ReadOnlySpan<T> sourceMemory = _arrays[^1].AsSpan();
var maxCount = Count % _blockSize;
sourceMemory = maxCount > 0
? sourceMemory[..maxCount]
: sourceMemory[.._blockSize];
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public IEnumerator<T> GetEnumerator() => _arrays.Enumerate(BlockSize, LongCount).GetEnumerator();
sourceMemory.CopyTo(arrayMemory);
}
}
public int IndexOf(T item) => (int)_arrays.LongIndexOf(BlockSize, item, _equalityComparer);
public IEnumerator<T> GetEnumerator() => EnumerateElements(_arrays, _blockSize, LongCount).GetEnumerator();
public void Insert(int index, T item) => throw new NotSupportedException();
public int IndexOf(T item)
{
for (var arrayIdx = 0; arrayIdx < _arrays.Count; arrayIdx++)
{
var array = _arrays[arrayIdx];
Span<T> arrayMemory = ((T[]?)array).AsSpan();
for (var memoryIdx = 0; memoryIdx < arrayMemory.Length; memoryIdx++)
{
if (_comparer.Equals(arrayMemory[memoryIdx], item))
{
return (arrayIdx * _blockSize) + memoryIdx;
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public long LongIndexOf(T item) => _arrays.LongIndexOf(BlockSize, item, _equalityComparer);
return -1;
}
public bool Remove(T item) => throw new NotSupportedException();
public void Insert(int index, T item)
{
throw new NotSupportedException();
}
public void RemoveAt(int index) => throw new NotSupportedException();
public bool Remove(T item)
{
throw new NotSupportedException();
}
public void RemoveAt(int index)
{
throw new NotSupportedException();
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => _arrays.Enumerate(BlockSize, LongCount).GetEnumerator();
protected virtual void Dispose(bool disposing)
{
@@ -226,10 +124,6 @@ namespace Recyclable.Collections
}
}
protected virtual void ListUpdated()
{
}
// override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
// ~RecyclableList()
// {
@@ -244,17 +138,17 @@ namespace Recyclable.Collections
GC.SuppressFinalize(this);
}
public void BeginUpdate() => _isUpdating++;
public void EndUpdate()
public void RemoveBlock(int index)
{
if (_isUpdating > 0)
try
{
_isUpdating--;
if (_isUpdating == 0)
{
ListUpdated();
}
_arrays[index].ReturnToPool();
_arrays.RemoveAt(index);
}
finally
{
Capacity -= BlockSize;
LongCount -= BlockSize;
}
}
}
@@ -1,8 +1,131 @@
namespace Recyclable.Collections
using System.Buffers;
using System.Runtime.CompilerServices;
namespace Recyclable.Collections
{
public static class RecyclableListExtensions
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void CopyTo<T>(this List<T[]> arrays, long startingIndex, int blockSize, int lastBlockSize, T[] destinationArray, int destinationArrayIndex)
{
Span<T> arrayMemory = destinationArray.AsSpan();
arrayMemory = arrayMemory[destinationArrayIndex..];
int startingArrayIndex = (int)(startingIndex / blockSize);
startingIndex %= blockSize;
if (arrays.Count > 0)
{
ReadOnlySpan<T> sourceMemory = arrays[0].AsSpan();
var maxToCopy = (int)Math.Min(blockSize - startingIndex, arrayMemory.Length);
if (arrays.Count == 1)
{
maxToCopy = (int)Math.Min(maxToCopy, lastBlockSize);
}
sourceMemory = sourceMemory[(int)startingIndex..maxToCopy];
sourceMemory.CopyTo(arrayMemory);
arrayMemory = arrayMemory[maxToCopy..];
}
for (var arrayIdx = startingArrayIndex + 1; arrayIdx < arrays.Count - 1 && arrayMemory.Length > 0; arrayIdx++)
{
ReadOnlySpan<T> sourceMemory = arrays[arrayIdx].AsSpan();
var maxToCopy = Math.Min(blockSize, arrayMemory.Length);
sourceMemory = sourceMemory[..maxToCopy];
sourceMemory.CopyTo(arrayMemory);
arrayMemory = arrayMemory[maxToCopy..];
}
if (arrays.Count > 1 && arrayMemory.Length > 0)
{
ReadOnlySpan<T> sourceMemory = arrays[^1].AsSpan();
var maxToCopy = Math.Min(lastBlockSize, arrayMemory.Length);
sourceMemory = sourceMemory[..maxToCopy];
sourceMemory.CopyTo(arrayMemory);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Contains<T>(this List<T[]> arrays, T item) => arrays.Any(x => x.Contains(item));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static IEnumerable<T> Enumerate<T>(this List<T[]> arrays, int chunkSize, long totalCount)
{
long currentCount = 0;
for (var arrayIdx = 0; arrayIdx < arrays.Count; arrayIdx++)
{
var array = arrays[arrayIdx];
currentCount += chunkSize;
switch (currentCount < totalCount)
{
case true:
for (var valueIdx = 0; valueIdx < chunkSize; valueIdx++)
{
yield return array[valueIdx];
}
break;
case false:
var partialCount = (int)(totalCount % chunkSize);
int maxCount = partialCount > 0 ? partialCount : chunkSize;
for (var valueIdx = 0; valueIdx < maxCount; valueIdx++)
{
yield return array[valueIdx];
}
break;
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T LimitTo<T>(this T value, T maxValue, IComparer<T> comparer)
=> comparer.Compare(value, maxValue) > 0 ? maxValue : value;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static long LongIndexOf<T>(this List<T[]> arrays, int blockSize, T item, IEqualityComparer<T> comparer)
{
for (var arrayIdx = 0; arrayIdx < arrays.Count; arrayIdx++)
{
var array = arrays[arrayIdx];
Span<T> arrayMemory = array.AsSpan();
for (int memoryIdx = 0; memoryIdx < arrayMemory.Length; memoryIdx++)
{
if (comparer.Equals(arrayMemory[memoryIdx], item))
{
return (arrayIdx * (long)blockSize) + memoryIdx;
}
}
}
return -1;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int ToArrayIndex(this long index, int blockSize) => (int)(index / blockSize);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static long ToItemIndex(this long index, int blockSize) => index % blockSize;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static RecyclableList<T> ToRecyclableList<T>(this IList<T> values, int blockSize = RecyclableDefaults.BlockSize) => new(values, blockSize);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static RecyclableList<T> ToRecyclableList<T>(this IEnumerable<T> values, int blockSize = RecyclableDefaults.BlockSize) => new(values, blockSize);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T[] RentArrayFromPool<T>(this int minSize) => ArrayPool<T>.Shared.Rent(minSize);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ReturnToPool<T>(this T[] array) => ArrayPool<T>.Shared.Return(array);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SetItem<T>(this List<T[]> arrays, in int blockSize, in long index, in T value)
=> arrays[(int)(index / blockSize)][index % blockSize] = value;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T GetItem<T>(this List<T[]> arrays, in int blockSize, in long index)
=> arrays[(int)(index / blockSize)][index % blockSize];
}
}
+188
View File
@@ -0,0 +1,188 @@
using System.Buffers;
using System.Collections;
using System.Runtime.CompilerServices;
namespace Recyclable.Collections
{
public class RecyclableQueue<T> : IRecyclableOwner<T>, IList<T>, IDisposable
{
private static readonly IEqualityComparer<T> _equalityComparer = EqualityComparer<T>.Default;
private static readonly IComparer<long> _comparer = Comparer<long>.Default;
private bool _disposedValue;
protected List<T[]> Arrays { get; }
protected long GetAbsoluteIndex(long index) => index + RemovedCount;
protected long GetRelativeIndex(long index) => index - RemovedCount;
protected long RemovedCount { get; set; }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static T[] RentArrayFromPool(int minSize) => ArrayPool<T>.Shared.Rent(minSize);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ReturnToPool(T[] array) => ArrayPool<T>.Shared.Return(array);
public RecyclableQueue(IEnumerable<T> source, int blockSize = RecyclableDefaults.BlockSize)
{
BlockSize = blockSize;
Arrays = new();
foreach (var item in source)
{
Enqueue(item);
}
}
public RecyclableQueue(int blockSize = RecyclableDefaults.BlockSize)
{
BlockSize = blockSize;
Arrays = new();
}
public int BlockSize { get; protected set; }
public long Capacity { get; set; }
public int Count
{
get => (int)LongCount.LimitTo(int.MaxValue, _comparer);
set => LongCount = value;
}
public long LongCount { get; set; }
public bool IsReadOnly { get; } = false;
public T this[int index] { get => this[(long)index]; set => this[(long)index] = value; }
public T this[long index]
{
get
{
long absoluteIndex = GetAbsoluteIndex(index);
int arrayIndex = absoluteIndex.ToArrayIndex(BlockSize);
long itemIndex = absoluteIndex.ToItemIndex(BlockSize);
return Arrays[arrayIndex][itemIndex];
}
set
{
long absoluteIndex = GetAbsoluteIndex(index);
int arrayIndex = absoluteIndex.ToArrayIndex(BlockSize);
long itemIndex = absoluteIndex.ToItemIndex(BlockSize);
Arrays[arrayIndex][itemIndex] = value;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Enqueue(T item)
{
if (LongCount == Capacity)
{
T[] newArray = BlockSize.RentArrayFromPool<T>();
Arrays.Add(newArray);
Capacity += BlockSize;
}
var newIndex = LongCount;
// We don't want this[newIndex] set to raise the update event, because
// the count would be wrong. We need to increase it, first.
this[newIndex] = item;
LongCount++;
}
public T Dequeue()
{
var toRemove = this[0];
RemovedCount++;
LongCount--;
if (Capacity - LongCount == BlockSize)
{
RemoveBlock(0);
RemovedCount -= BlockSize;
}
return toRemove;
}
public void Add(T item) => Enqueue(item);
public void Clear()
{
try
{
// Remove in reversed order for performance savings
while (Arrays.Count > 0)
{
try
{
RemoveBlock(Arrays.Count - 1);
}
catch (Exception)
{
// We want to try returning as many arrays, as possible, before
// the list is cleared.
}
}
}
finally
{
LongCount = 0;
RemovedCount = 0;
Arrays.Clear();
}
}
public bool Contains(T item) => Arrays.Contains(item);
public void CopyTo(T[] array, int arrayIndex) => Arrays.CopyTo(RemovedCount, BlockSize, (int)(LongCount % BlockSize), array, arrayIndex);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public IEnumerable<T> Enumerate() => Arrays.Enumerate(BlockSize, LongCount);
public IEnumerator<T> GetEnumerator() => Enumerate().GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public int IndexOf(T item) => (int)LongIndexOf(item).LimitTo(int.MaxValue, _comparer);
public long LongIndexOf(T item) => GetRelativeIndex(Arrays.LongIndexOf(BlockSize, item, _equalityComparer));
public void Insert(int index, T item) => throw new NotSupportedException();
public void RemoveAt(int index) => throw new NotSupportedException();
public bool TryDequeue(out T? item)
{
if (LongCount > 0)
{
item = Dequeue();
return true;
}
item = default;
return false;
}
public bool Remove(T item) => throw new NotSupportedException();
public void RemoveBlock(int index)
{
try
{
ReturnToPool(Arrays[index]);
Arrays.RemoveAt(index);
}
finally
{
Capacity -= BlockSize;
LongCount -= BlockSize;
}
}
protected void Dispose(bool disposing)
{
if (!_disposedValue)
{
Clear();
_disposedValue = true;
}
}
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
}
+42 -6
View File
@@ -4,11 +4,11 @@
where TKey : notnull
{
private static readonly IComparer<(TKey, TValue)> _comparer = new FrequencyTupleComparer<TKey, TValue>();
private readonly SystemRandomNumberGenerator _randomNumberGenerator = new SystemRandomNumberGenerator();
private uint _isUpdating;
private readonly SystemRandomNumberGenerator _randomNumberGenerator = new();
protected override void ListUpdated()
protected void ListUpdated()
{
base.ListUpdated();
QuickSort();
}
@@ -27,17 +27,53 @@
QuickSort();
}
public RecyclableSortedList(int blockSize = RecyclableDefaults.BlockSize)
: base(blockSize)
{
}
public bool IsUpdating => _isUpdating > 0;
public void Add(TKey key, TValue value) => Add((key, value));
public void BeginUpdate() => _isUpdating++;
public void Clear()
{
BeginUpdate();
try
{
base.Clear();
}
finally
{
EndUpdate();
}
}
public void EndUpdate(bool raiseListUpdated = true)
{
if (_isUpdating > 0)
{
_isUpdating--;
if (raiseListUpdated && _isUpdating == 0)
{
ListUpdated();
}
}
}
public void QuickSort()
{
_isUpdating++;
BeginUpdate();
try
{
this.QuickSort(_comparer, _randomNumberGenerator);
}
finally
{
_isUpdating--;
EndUpdate(false);
}
}
}
}
}
+86
View File
@@ -0,0 +1,86 @@
using System.Collections;
namespace Recyclable.Collections
{
public class RecyclableStack<T> : IList<T>, IDisposable
{
private bool _disposedValue;
private readonly int _blockSize;
protected RecyclableList<T> List { get; }
public int Count => List.Count;
public long LongCount
{
get => List.LongCount;
set => List.LongCount = value;
}
public bool IsReadOnly => List.IsReadOnly;
public T this[int index] { get => this[(long)index]; set => this[(long)index] = value; }
public T this[long index] { get => List[index]; set => List[index] = value; }
public RecyclableStack(int blockSize = RecyclableDefaults.BlockSize)
{
_blockSize = blockSize;
List = new(blockSize);
}
public RecyclableStack(IEnumerable<T> list, int blockSize = RecyclableDefaults.BlockSize)
{
_blockSize = blockSize;
List = new(list, blockSize);
}
public RecyclableStack(RecyclableList<T> list, int blockSize = RecyclableDefaults.BlockSize)
{
_blockSize = blockSize;
List = new(list, blockSize);
}
public void Push(T item)
{
List.Add(item);
}
public T Pop()
{
var toRemove = List[LongCount - 1];
LongCount--;
if (List.Capacity - LongCount == _blockSize)
{
List.RemoveBlock(List.BlockCount - 1);
}
return toRemove;
}
public int IndexOf(T item) => List.IndexOf(item);
public long LongIndexOf(T item) => List.LongIndexOf(item);
public void Insert(int index, T item) => List.Insert(index, item);
public void RemoveAt(int index) => List.RemoveAt(index);
public void Add(T item) => List.Add(item);
public void Clear() => List.Clear();
public bool Contains(T item) => List.Contains(item);
public void CopyTo(T[] array, int arrayIndex) => List.CopyTo(array, arrayIndex);
public bool Remove(T item) => List.Remove(item);
public IEnumerator<T> GetEnumerator() => List.Reverse().GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
protected virtual void Dispose(bool disposing)
{
if (!_disposedValue)
{
Clear();
_disposedValue = true;
}
}
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
}
@@ -2,7 +2,7 @@
{
public class SystemRandomNumberGenerator : IRandomNumberGenerator
{
private static readonly Random _random = new();
private readonly Random _random = new();
public int NextInt32(int startIndex, int endIndex) => _random.Next(startIndex, endIndex);
}