RecyclableList.Enumerator performance improvements to match List<T>'s performance. Dirty state.

This commit is contained in:
mlemanczyk committed 2023-06-19 22:02:11 +02:00
1 parent 51d3613ffd
commit 15cac80d2f
13 files changed
+683 -192

No files matched your search

@@ -0,0 +1,45 @@
namespace Recyclable.Collections.Benchmarks
{
public partial class RecyclableCollectionsBenchmarks
{
public void Array_ForEach()
{
foreach (var item in TestObjects)
{
DoNothing(item);
}
}
public void List_ForEach()
{
foreach (var item in TestObjectsAsList)
{
DoNothing(item);
}
}
public void PooledList_ForEach()
{
foreach (var item in TestObjectsAsPooledList)
{
DoNothing(item);
}
}
public void RecyclableList_ForEach()
{
foreach (var item in TestObjectsAsRecyclableList)
{
DoNothing(item);
}
}
public void RecyclableLongList_ForEach()
{
foreach (var item in TestObjectsAsRecyclableLongList)
{
DoNothing(item);
}
}
}
}
@@ -5,9 +5,9 @@ namespace Recyclable.Collections.Benchmarks
{
public enum RecyclableCollectionsBenchmarkType
{
Add_WithCapacity, Add, AddRange_WithCapacity, AddRangeWhenSourceIsArray, AddRangeWhenSourceIsIEnumerable,
Unknown, Add_WithCapacity, Add, AddRange_WithCapacity, AddRangeWhenSourceIsArray, AddRangeWhenSourceIsIEnumerable,
AddRangeWhenSourceIsIList, AddRangeWhenSourceIsList, AddRangeWhenSourceIsSameType, Contains_FirstItems,
Contains_LastItems, Count, Create_WithCapacity, Create, GetItem, IndexOf_BestAndWorstCases, IndexOf_FirstItems,
Contains_LastItems, Count, Create_WithCapacity, Create, ForEach, GetItem, IndexOf_BestAndWorstCases, IndexOf_FirstItems,
IndexOf_LastItems, LongCount, Remove_FirstItems, Remove_LastItems, RemoveAt_FirstItems, RemoveAt_LastItems,
SetItem,
}
@@ -17,52 +17,53 @@ namespace Recyclable.Collections.Benchmarks
public RecyclableCollectionsBenchmarks() : base()
{
TestObjectCount = 1;
BaseDataType = RecyclableCollectionsBenchmarkSource.List;
DataType = RecyclableCollectionsBenchmarkSource.RecyclableLongList;
//BaseDataType = RecyclableCollectionsBenchmarkSource.List;
DataType = RecyclableCollectionsBenchmarkSource.RecyclableList;
}
[Params
(
RecyclableCollectionsBenchmarkType.Add_WithCapacity,
RecyclableCollectionsBenchmarkType.Add,
RecyclableCollectionsBenchmarkType.AddRange_WithCapacity,
RecyclableCollectionsBenchmarkType.AddRangeWhenSourceIsArray,
RecyclableCollectionsBenchmarkType.AddRangeWhenSourceIsIEnumerable,
RecyclableCollectionsBenchmarkType.AddRangeWhenSourceIsIList,
RecyclableCollectionsBenchmarkType.AddRangeWhenSourceIsList,
RecyclableCollectionsBenchmarkType.AddRangeWhenSourceIsSameType,
RecyclableCollectionsBenchmarkType.Contains_FirstItems,
RecyclableCollectionsBenchmarkType.Contains_LastItems,
RecyclableCollectionsBenchmarkType.Count,
RecyclableCollectionsBenchmarkType.Create_WithCapacity,
RecyclableCollectionsBenchmarkType.Create,
RecyclableCollectionsBenchmarkType.GetItem,
RecyclableCollectionsBenchmarkType.IndexOf_BestAndWorstCases,
RecyclableCollectionsBenchmarkType.IndexOf_FirstItems,
RecyclableCollectionsBenchmarkType.IndexOf_LastItems,
RecyclableCollectionsBenchmarkType.LongCount,
RecyclableCollectionsBenchmarkType.Remove_FirstItems,
RecyclableCollectionsBenchmarkType.Remove_LastItems,
RecyclableCollectionsBenchmarkType.RemoveAt_FirstItems,
RecyclableCollectionsBenchmarkType.RemoveAt_LastItems,
RecyclableCollectionsBenchmarkType.SetItem
//RecyclableCollectionsBenchmarkType.Add_WithCapacity,
//RecyclableCollectionsBenchmarkType.Add,
//RecyclableCollectionsBenchmarkType.AddRange_WithCapacity,
//RecyclableCollectionsBenchmarkType.AddRangeWhenSourceIsArray,
//RecyclableCollectionsBenchmarkType.AddRangeWhenSourceIsIEnumerable,
//RecyclableCollectionsBenchmarkType.AddRangeWhenSourceIsIList,
//RecyclableCollectionsBenchmarkType.AddRangeWhenSourceIsList,
//RecyclableCollectionsBenchmarkType.AddRangeWhenSourceIsSameType,
//RecyclableCollectionsBenchmarkType.Contains_FirstItems,
//RecyclableCollectionsBenchmarkType.Contains_LastItems,
//RecyclableCollectionsBenchmarkType.Count,
//RecyclableCollectionsBenchmarkType.Create_WithCapacity,
//RecyclableCollectionsBenchmarkType.Create,
RecyclableCollectionsBenchmarkType.ForEach
//RecyclableCollectionsBenchmarkType.GetItem,
//RecyclableCollectionsBenchmarkType.IndexOf_BestAndWorstCases,
//RecyclableCollectionsBenchmarkType.IndexOf_FirstItems,
//RecyclableCollectionsBenchmarkType.IndexOf_LastItems,
//RecyclableCollectionsBenchmarkType.LongCount,
//RecyclableCollectionsBenchmarkType.Remove_FirstItems,
//RecyclableCollectionsBenchmarkType.Remove_LastItems,
//RecyclableCollectionsBenchmarkType.RemoveAt_FirstItems,
//RecyclableCollectionsBenchmarkType.RemoveAt_LastItems,
//RecyclableCollectionsBenchmarkType.SetItem
)]
public RecyclableCollectionsBenchmarkType TestCase { get; set; } = RecyclableCollectionsBenchmarkType.IndexOf_BestAndWorstCases;
[Params
(
RecyclableCollectionsBenchmarkSource.List
)]
//[Params
//(
// RecyclableCollectionsBenchmarkSource.List
//)]
public override RecyclableCollectionsBenchmarkSource BaseDataType { get => base.BaseDataType; set => base.BaseDataType = value; }
[Params
(
RecyclableCollectionsBenchmarkSource.Array,
RecyclableCollectionsBenchmarkSource.List,
RecyclableCollectionsBenchmarkSource.PooledList,
RecyclableCollectionsBenchmarkSource.RecyclableList,
RecyclableCollectionsBenchmarkSource.RecyclableLongList
//RecyclableCollectionsBenchmarkSource.Array,
//RecyclableCollectionsBenchmarkSource.List,
//RecyclableCollectionsBenchmarkSource.PooledList,
RecyclableCollectionsBenchmarkSource.RecyclableList
//RecyclableCollectionsBenchmarkSource.RecyclableLongList
)]
public override RecyclableCollectionsBenchmarkSource DataType { get => base.DataType; set => base.DataType = value; }
@@ -50,7 +50,7 @@ namespace Recyclable.Collections.Benchmarks.POC
// [Params(1, 10, 1024, 10240, RecyclableDefaults.MinPooledArrayLength, RecyclableDefaults.MinItemsCountForParallelization)]
// [Params(RecyclableDefaults.MinItemsCountForParallelization * 3)]
// [Params(1_048_576)]
public virtual int TestObjectCount { get; set; } = 1; //33_554_432; //524_288; //131_072;
public virtual int TestObjectCount { get; set; } = 131_072; //1; //33_554_432; //524_288; //131_072;
public virtual int TestObjectCountForSlowMethods => (TestObjectCount / 2) + 1;// Math.Min(TestObjectCount, 131_072);
// [Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160, 170, 180, 190, 200)]
+8 -9
View File
@@ -23,17 +23,16 @@ public static class Program
// ****************
// *** Template ***
// ****************
//var benchmark = new RecyclableCollectionsBenchmarks();
//benchmark.Setup();
var benchmark = new RecyclableCollectionsBenchmarks();
benchmark.Setup();
//foreach (var _ in Enumerable.Range(1, 1000))
//{
// benchmark.RecyclableLongList_Remove_FirstItems();
// benchmark.RecyclableLongList_RemoveAt_LastItems();
// benchmark.List_RemoveAt_LastItems();
benchmark.List_ForEach();
benchmark.RecyclableList_ForEach();
//}
//benchmark.Cleanup();
benchmark.Cleanup();
}
static void RunPocBenchmarks()
@@ -53,8 +52,8 @@ public static class Program
static void Main(string[] args)
{
RunRecyclableCollectionsBenchmarks();
// RunPocBenchmarks();
// RunSelectedBenchmarks();
// RunAssemblyBenchmarks();
//RunPocBenchmarks();
//RunSelectedBenchmarks();
//RunAssemblyBenchmarks();
}
}
@@ -1,20 +1,15 @@
using Microsoft.Extensions.ObjectPool;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Recyclable.Collections.Pools
{
public class RecyclableCollectionVersionPool
{
private static readonly RecyclableCollectionVersionPool _shared = new();
public static RecyclableCollectionVersionPool Shared => _shared;
//public class RecyclableCollectionVersionPool
//{
// private static readonly RecyclableCollectionVersionPool _shared = new();
// public static RecyclableCollectionVersionPool Shared => _shared;
private ObjectPool<RecyclableCollectionVersion> _pool = new DefaultObjectPool<RecyclableCollectionVersion>(new DefaultPooledObjectPolicy<RecyclableCollectionVersion>());
// private readonly ObjectPool<RecyclableCollectionVersion> _pool = new DefaultObjectPool<RecyclableCollectionVersion>(new DefaultPooledObjectPolicy<RecyclableCollectionVersion>());
public RecyclableCollectionVersion Get() => _pool.Get().Reset();
public void Return(RecyclableCollectionVersion version) => _pool.Return(version);
}
// public RecyclableCollectionVersion Get() => _pool.Get().Reset();
// public void Return(RecyclableCollectionVersion version) => _pool.Return(version);
//}
}
@@ -0,0 +1,19 @@
using Microsoft.Extensions.ObjectPool;
namespace Recyclable.Collections
{
public partial class RecyclableList<T>
{
//internal class RecyclableListEnumeratorPool
//{
// private static readonly RecyclableListEnumeratorPool _shared = new();
// public static RecyclableListEnumeratorPool Shared => _shared;
// private readonly ObjectPool<RecyclableListEnumerator> _pool = new DefaultObjectPool<RecyclableListEnumerator>(new DefaultPooledObjectPolicy<RecyclableListEnumerator>());
// public RecyclableListEnumerator Get(RecyclableList<T> list) => _pool.Get().Reset(this, list);
// public void Return(RecyclableListEnumerator version) => _pool.Return(version);
//}
}
}
@@ -2,39 +2,88 @@
namespace Recyclable.Collections
{
public delegate void VersionChangedEventHandler();
public sealed class RecyclableCollectionVersion
{
private ulong _enumeratorCount;
public ulong Version;
//private ulong _enumeratorCount;
//private ulong _version;
//public ulong Version
//{
// get => _version;
// set
// {
// _version = value;
// _versionChanged?.Invoke(this, EventArgs.Empty);
// }
//}
//public uint Version { get; set; }
public bool IsVersioned;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Inc() => _ = unchecked(Version++);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddEnumerator()
private VersionChangedEventHandler? _versionChanged;
public event VersionChangedEventHandler VersionChanged
{
if (unchecked(_enumeratorCount++) == 0)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
add
{
_versionChanged += value;
if (!IsVersioned)
{
IsVersioned = true;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
remove
{
IsVersioned = true;
_versionChanged -= value;
if (_versionChanged == null)
{
IsVersioned = false;
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void RemoveEnumerator()
public void Inc()
{
if (unchecked(--_enumeratorCount) == 0)
if (IsVersioned)
{
IsVersioned = false;
_versionChanged!();
}
//_ = unchecked(Version++);
}
//[MethodImpl(MethodImplOptions.AggressiveInlining)]
//public RecyclableCollectionVersion AddEnumerator()
//{
// if (unchecked(_enumeratorCount++) == 0)
// {
// IsVersioned = true;
// }
// return this;
//}
//[MethodImpl(MethodImplOptions.AggressiveInlining)]
//public void RemoveEnumerator()
//{
// if (unchecked(--_enumeratorCount) == 0)
// {
// IsVersioned = false;
// }
//}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public RecyclableCollectionVersion Reset()
{
IsVersioned = false;
Version = 0;
_enumeratorCount = 0;
//Version = 0;
//_enumeratorCount = 0;
return this;
}
}
@@ -1,79 +1,166 @@
using System.Collections;
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
using System.Runtime.ConstrainedExecution;
namespace Recyclable.Collections
{
public class RecyclableListEnumerator<T> : IEnumerator<T>
public partial class RecyclableList<T>
{
private static void ThrowListModifiedException() => throw new InvalidOperationException("Can't move to the next item, because the collection was modified. You must restart the enumeration by calling Reset(), if you want to enumerate the collection again");
private int _currentIndex = RecyclableDefaults.ItemNotFoundIndex;
private RecyclableList<T>? _list;
private RecyclableCollectionVersion? _listVersion;
private ulong _version;
private T? _current;
public RecyclableListEnumerator(RecyclableList<T> list)
public struct Enumerator : IEnumerator<T>, IEnumerator
{
_list = list;
_listVersion = list._version;
_listVersion!.AddEnumerator();
_version = _listVersion!.Version;
}
#nullable disable
private readonly RecyclableList<T> _list;
private int _currentIndex;
private T _current;
public T Current => _current ?? throw new InvalidOperationException("The enumerator wasn't initialized. You need to call MoveNext() before getting the current iterator value");
object? IEnumerator.Current => _current;
//[Pure]
//private readonly int _listCount;
//private readonly int _listCountMinus1;
//private readonly T[] _memoryBlock;
//private bool _endOfCollectionReached;
public void Dispose()
{
if (_listVersion != null)
//private RecyclableCollectionVersion _listVersion;
//private ulong _enumeratorVersion;
//private bool _isVersionChanged;
//private void VersionChanged() => _isVersionChanged = true;
//internal RecyclableListEnumeratorPool? _pool;
//internal bool _returned;
//public RecyclableListEnumerator()
//{
//}
//[Pure]
internal Enumerator(RecyclableList<T> list)
{
_listVersion.RemoveEnumerator();
_listVersion = null;
_current = default;
_currentIndex = 0;
_list = list;
//_memoryBlock = list._memoryBlock;
//_listCount = list._count;
//_listVersion = list._version!;
//_listVersion.VersionChanged += VersionChanged;
//_enumeratorVersion = _listVersion.Version;
}
if (_list != null)
//{
// //[MethodImpl(MethodImplOptions.AggressiveInlining)]
// get =>
// //if (_currentIndex < 0)
// //{
// // ThrowHelper.ThrowInvalidOperationException("The enumerator wasn't initialized. You need to call MoveNext() before getting the current iterator value");
// //}
// //else
// //if (_endOfCollectionReached)
// //{
// // throw new InvalidOperationException("The enumerator reached the end of the collection. You need to call Reset(), if you want to enumerate the collection again");
// //}
// //else
// //{
// //if (_currentIndex < 0)
// //{
// // ThrowHelper.ThrowInvalidOperationException("The enumerator wasn't initialized. You need to call MoveNext() before getting the current iterator value");
// //}
// //else
// _memoryBlock[_currentIndex];//}
//}
//[Pure]
public readonly void Dispose()
{
_list = null;
// if (_listVersion != null)
// {
// _listVersion.VersionChanged -= VersionChanged;
// //_listVersion.RemoveEnumerator(this);
//#nullable disable
// _listVersion = null;
//#nullable restore
// }
//if (_pool != null)
//{
// if (!_returned)
// {
// _returned = true;
// _pool?.Return(this);
// }
//}
//else
//{
//_memoryBlock = null;
//GC.SuppressFinalize(this);
//}
}
GC.SuppressFinalize(this);
}
public bool MoveNext()
{
if (_list != null)
//[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool MoveNext()
{
if (_list._version!.Version != _version)
{
ThrowListModifiedException();
return false;
}
else if (_currentIndex < _list.Count - 1)
var list = _list;
//if (_isVersionChanged)
//{
// ThrowHelper.ThrowListModifiedException();
// return false;
//}
//else
if (_currentIndex < list._count)
{
_current = list._memoryBlock[_currentIndex];
_currentIndex++;
_current = _list[_currentIndex];
return true;
}
else
{
return false;
}
}
else
{
//else if (!_endOfCollectionReached)
//{
// _endOfCollectionReached = true;
// return false;
//}
_current = default;
return false;
}
}
public void Reset()
{
_currentIndex = RecyclableDefaults.ItemNotFoundIndex;
_current = default;
if (_list != null)
#nullable restore
public T Current => _current;
object IEnumerator.Current => _current;
#nullable disable
//[MethodImpl(MethodImplOptions.AggressiveInlining)]
void IEnumerator.Reset()
{
_version = _list._version!.Version;
_currentIndex = 0;
_current = default;
}
//_endOfCollectionReached = false;
//if (_isVersionChanged)
//{
//
//_enumeratorVersion = _listVersion!.Version;
// _isVersionChanged = false;
//}
//internal RecyclableListEnumerator Reset(RecyclableListEnumeratorPool pool, RecyclableList<T> list)
//{
// _returned = false;
// if (_pool != pool)
// {
// _pool = pool;
// }
// _currentIndex = RecyclableDefaults.ItemNotFoundIndex;
// _listVersion = list._version!;
// _enumeratorVersion = _listVersion!.Version;
// list._version!.AddEnumerator();
// return this;
//}
//[Pure]
//public object Clone() => MemberwiseClone();
}
}
#nullable restore
}
@@ -7,7 +7,9 @@ namespace Recyclable.Collections
public bool IsFixedSize => false;
public bool IsSynchronized => false;
[NonSerialized]
private object? _syncRoot;
public object SyncRoot
{
get
@@ -39,7 +41,7 @@ namespace Recyclable.Collections
}
else
{
ThrowArgumentOutOfRangeException(nameof(value), $"Argument is invalid. {(value != null ? "List element type is incompatible with the given value" : "Element is null and the list doesn't allow null values.")}");
ThrowHelper.ThrowArgumentOutOfRangeException(nameof(value), $"Argument is invalid. {(value != null ? "List element type is incompatible with the given value" : "Element is null and the list doesn't allow null values.")}");
}
}
}
@@ -58,7 +60,7 @@ namespace Recyclable.Collections
}
else
{
ThrowArgumentOutOfRangeException(nameof(value), $"Argument is invalid. {(value != null ? "List element type is incompatible with the given value" : "Element is null and the list doesn't allow null values.")}");
ThrowHelper.ThrowArgumentOutOfRangeException(nameof(value), $"Argument is invalid. {(value != null ? "List element type is incompatible with the given value" : "Element is null and the list doesn't allow null values.")}");
return RecyclableDefaults.ItemNotFoundIndex;
}
}
@@ -91,7 +93,7 @@ namespace Recyclable.Collections
}
else
{
ThrowArgumentOutOfRangeException(nameof(value), $"Argument is invalid. {(value != null ? "List element type is incompatible with the given value" : "Element is null and the list doesn't allow null values.")}");
ThrowHelper.ThrowArgumentOutOfRangeException(nameof(value), $"Argument is invalid. {(value != null ? "List element type is incompatible with the given value" : "Element is null and the list doesn't allow null values.")}");
}
}
@@ -109,7 +111,7 @@ namespace Recyclable.Collections
}
else
{
ThrowArgumentOutOfRangeException(nameof(value), $"Argument is invalid. {(value != null ? "List element type is incompatible with the given value" : "Element is null and the list doesn't allow null values.")}");
ThrowHelper.ThrowArgumentOutOfRangeException(nameof(value), $"Argument is invalid. {(value != null ? "List element type is incompatible with the given value" : "Element is null and the list doesn't allow null values.")}");
}
}
+31 -34
View File
@@ -5,13 +5,13 @@ using System.Runtime.CompilerServices;
namespace Recyclable.Collections
{
public partial class RecyclableList<T> : IList<T>, IReadOnlyList<T>, IDisposable
[Serializable]
public sealed partial class RecyclableList<T> : IList<T>, IReadOnlyList<T>, IList, IDisposable
{
private static readonly ArrayPool<T> _arrayPool = ArrayPool<T>.Create();
private static readonly bool _defaultIsNull = default(T) == null;
protected static readonly bool NeedsClearing = !typeof(T).IsValueType;
private static void ThrowArgumentOutOfRangeException(in string argumentName, in string message) => throw new ArgumentOutOfRangeException(argumentName, message);
public static explicit operator ReadOnlySpan<T>(RecyclableList<T> source) => new(source._memoryBlock, 0, source.Count);
protected T[] _memoryBlock;
@@ -224,23 +224,23 @@ namespace Recyclable.Collections
}
protected int _count;
public int Count
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _count;
public int Count => _count;
//{
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
// get => _count;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
// TODO: Clear items if we're decreasing the size of the list, if T is reference type
set
{
_count = value;
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
// // TODO: Clear items if we're decreasing the size of the list, if T is reference type
// set
// {
// _count = value;
if (_version?.IsVersioned ?? false)
{
_version.Inc();
}
}
}
// if (_version?.IsVersioned ?? false)
// {
// _version.Inc();
// }
// }
//}
public bool IsReadOnly => false;
@@ -397,12 +397,12 @@ namespace Recyclable.Collections
var sourceItemsCount = items.LongCount;
if (sourceItemsCount > int.MaxValue)
{
ThrowArgumentOutOfRangeException(nameof(items), $"The number of items exceeds the maximum capacity of {nameof(RecyclableList<T>)}, equal {int.MaxValue}, equal {int.MaxValue}. Please consider using {nameof(RecyclableLongList<T>)}, instead");
ThrowHelper.ThrowArgumentOutOfRangeException(nameof(items), $"The number of items exceeds the maximum capacity of {nameof(RecyclableList<T>)}, equal {int.MaxValue}, equal {int.MaxValue}. Please consider using {nameof(RecyclableLongList<T>)}, instead");
}
if (_count + sourceItemsCount > int.MaxValue)
{
ThrowArgumentOutOfRangeException(nameof(items), $"The total number of items in source and target table exceeds the maximum capacity of {nameof(RecyclableList<T>)}, equal {int.MaxValue}. Please consider using {nameof(RecyclableLongList<T>)}, instead");
ThrowHelper.ThrowArgumentOutOfRangeException(nameof(items), $"The total number of items in source and target table exceeds the maximum capacity of {nameof(RecyclableList<T>)}, equal {int.MaxValue}. Please consider using {nameof(RecyclableLongList<T>)}, instead");
}
int targetCapacity = _count + (int)sourceItemsCount;
@@ -571,7 +571,7 @@ namespace Recyclable.Collections
{
if (index >= _count)
{
ThrowArgumentOutOfRangeException(nameof(index), $"Provided value {index} exceeds the no. of items on the list {_count}");
ThrowHelper.ThrowArgumentOutOfRangeException(nameof(index), $"Provided value {index} exceeds the no. of items on the list {_count}");
}
_count--;
@@ -593,19 +593,16 @@ namespace Recyclable.Collections
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public IEnumerator<T> GetEnumerator()
{
_version ??= RecyclableCollectionVersionPool.Shared.Get();
return new RecyclableListEnumerator<T>(this);
}
public Enumerator GetEnumerator() => new Enumerator(this);
//[MethodImpl(MethodImplOptions.AggressiveInlining)]
IEnumerator IEnumerable.GetEnumerator() =>
//_version ??= new(); // RecyclableCollectionVersionPool.Shared.Get();
new Enumerator(this);//return RecyclableListEnumeratorPool.Shared.Get(this);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
IEnumerator IEnumerable.GetEnumerator()
{
_version ??= RecyclableCollectionVersionPool.Shared.Get();
return new RecyclableListEnumerator<T>(this);
}
//[MethodImpl(MethodImplOptions.AggressiveInlining)]
IEnumerator<T> IEnumerable<T>.GetEnumerator() =>
//_version ??= new(); // RecyclableCollectionVersionPool.Shared.Get();
new Enumerator(this);//return RecyclableListEnumeratorPool.Shared.Get(this);
public void Dispose()
{
@@ -620,9 +617,9 @@ namespace Recyclable.Collections
if (_version != null)
{
var toReturn = _version;
//var toReturn = _version;
_version = null;
RecyclableCollectionVersionPool.Shared.Return(toReturn);
//RecyclableCollectionVersionPool.Shared.Return(toReturn);
}
GC.SuppressFinalize(this);
@@ -0,0 +1,149 @@
using System.Collections;
namespace Recyclable.Collections
{
public sealed class RecyclableLongListEnumerator<T> : IEnumerator<T>
{
public static void ThrowListModifiedException() => throw new InvalidOperationException("Can't move to the next item, because the collection was modified. You must restart the enumeration by calling Reset(), if you want to enumerate the collection again");
private int _blockSize;
private int _blockSizeMinus1;
private int _currentBlockIndex = 0;
private int _currentIndex = RecyclableDefaults.ItemNotFoundIndex;
private int _lastBlockWithData;
private RecyclableLongList<T>? _list;
private RecyclableCollectionVersion? _listVersion;
private T[][] _memoryBlocks;
private int _nextItemBlockIndex;
private int _nextItemIndex;
//private ulong _version;
private bool _isVersionChanged;
private void VersionChanged() => _isVersionChanged = true;
private T? _current;
public RecyclableLongListEnumerator(RecyclableLongList<T> list)
{
_listVersion = list._version;
list._version!.VersionChanged += VersionChanged;
//_listVersion!.AddEnumerator();
_blockSize = list._blockSize;
_blockSizeMinus1 = list._blockSizeMinus1;
_lastBlockWithData = list._lastBlockWithData;
_list = list;
//_version = _listVersion!.Version;
_memoryBlocks = list._memoryBlocks;
_nextItemBlockIndex = list._nextItemBlockIndex;
_nextItemIndex = _lastBlockWithData == _nextItemBlockIndex ? _list._nextItemIndex : _blockSize;
}
public T Current => _current ?? throw new InvalidOperationException("The enumerator wasn't initialized. You need to call MoveNext() before getting the current iterator value");
object? IEnumerator.Current => _current;
public void Dispose()
{
if (_listVersion != null)
{
_listVersion.VersionChanged -= VersionChanged;
//_listVersion.RemoveEnumerator();
_listVersion = null;
}
if (_list != null)
{
_list = null;
}
GC.SuppressFinalize(this);
}
public bool MoveNext()
{
if (_list != null)
{
if (_isVersionChanged)
{
_current = default;
ThrowListModifiedException();
return false;
}
else if (_currentBlockIndex < _lastBlockWithData)
{
if (_currentIndex < _blockSizeMinus1)
{
_currentIndex++;
}
else
{
_currentIndex = 0;
_currentBlockIndex++;
if (_currentBlockIndex == _lastBlockWithData && _currentIndex >= _nextItemIndex)
{
_current = default;
return false;
}
}
_current = _memoryBlocks[_currentBlockIndex][_currentIndex];
return true;
}
else if (_currentBlockIndex == _lastBlockWithData && !(_currentIndex >= _nextItemIndex - 1))
{
_currentIndex++;
_current = _memoryBlocks[_currentBlockIndex][_currentIndex];
return true;
}
else
{
_current = default;
return false;
}
}
else
{
_current = default;
return false;
}
}
public void Reset()
{
_currentBlockIndex = 0;
_currentIndex = RecyclableDefaults.ItemNotFoundIndex;
_current = default;
if (_isVersionChanged)
{
//_blockSize = _list!._blockSize;
//_blockSizeMinus1 = _list._blockSizeMinus1;
_isVersionChanged = false;
//_lastBlockWithData = _list!._lastBlockWithData;
//_listVersion = _list._version;
//_memoryBlocks = _list._memoryBlocks;
//_nextItemBlockIndex = _list._nextItemBlockIndex;
//_nextItemIndex = _lastBlockWithData == _nextItemBlockIndex ? _list._nextItemIndex : _blockSize;
//_version = _listVersion!.Version;
}
}
internal RecyclableLongListEnumerator<T> Init()
{
_currentBlockIndex = 0;
_currentIndex = RecyclableDefaults.ItemNotFoundIndex;
_current = default;
if (_list != null)
{
_blockSize = _list._blockSize;
_blockSizeMinus1 = _list._blockSizeMinus1;
_isVersionChanged = false;
_lastBlockWithData = _list._lastBlockWithData;
_listVersion = _list._version;
_memoryBlocks = _list._memoryBlocks;
_nextItemBlockIndex = _list._nextItemBlockIndex;
_nextItemIndex = _lastBlockWithData == _nextItemBlockIndex ? _list._nextItemIndex : _blockSize;
//_version = _listVersion!.Version;
}
return this;
}
}
}
+155 -30
View File
@@ -22,7 +22,7 @@ namespace Recyclable.Collections
internal int _blockSize;
internal byte _blockSizePow2BitShift;
internal int _blockSizeMinus1;
private int _nextItemBlockIndex;
internal int _nextItemBlockIndex;
internal int _nextItemIndex;
private int _reservedBlockCount;
@@ -36,12 +36,34 @@ namespace Recyclable.Collections
public long Capacity
{
get => _capacity;
protected set => _capacity = value;
protected set
{
_capacity = value;
if (_version?.IsVersioned ?? false)
{
_version.Inc();
}
}
}
public int Count => checked((int)_longCount);
public bool IsReadOnly { get; }
private int _lastBlockWithData = RecyclableDefaults.ItemNotFoundIndex;
public int Count
{
get => checked((int)_longCount);
set
{
_longCount = value;
if (_version?.IsVersioned ?? false)
{
_version.Inc();
}
}
}
public bool IsReadOnly => false;
internal int _lastBlockWithData = RecyclableDefaults.ItemNotFoundIndex;
public int LastBlockWithData => _lastBlockWithData;
internal long _longCount;
@@ -49,7 +71,15 @@ namespace Recyclable.Collections
public long LongCount
{
get => _longCount;
set => _longCount = value;
set
{
_longCount = value;
if (_version?.IsVersioned ?? false)
{
_version.Inc();
}
}
}
public int ReservedBlockCount => _reservedBlockCount;
@@ -58,6 +88,9 @@ namespace Recyclable.Collections
public byte BlockSizePow2BitShift => _blockSizePow2BitShift;
public int NextItemBlockIndex => _nextItemBlockIndex;
public int NextItemIndex => _nextItemIndex;
internal RecyclableCollectionVersion? _version;
public RecyclableCollectionVersion? Version => _version;
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
private static void CopyFollowingItems(RecyclableLongList<T> list, long destinationItemIndex)
@@ -631,16 +664,32 @@ namespace Recyclable.Collections
public T this[long index]
{
get => _memoryBlocks[index >> _blockSizePow2BitShift][index & _blockSizeMinus1];
set => new Span<T>(_memoryBlocks[checked((int)(index >> _blockSizePow2BitShift))])[checked((int)(index & _blockSizeMinus1))] = value;
set
{
new Span<T>(_memoryBlocks[checked((int)(index >> _blockSizePow2BitShift))])[checked((int)(index & _blockSizeMinus1))] = value;
if (_version?.IsVersioned ?? false)
{
_version.Inc();
}
}
}
public T this[int index]
{
get => _memoryBlocks[index >> _blockSizePow2BitShift][index & _blockSizeMinus1];
set => new Span<T>(_memoryBlocks[index >> _blockSizePow2BitShift])[index & _blockSizeMinus1] = value;
set
{
new Span<T>(_memoryBlocks[index >> _blockSizePow2BitShift])[index & _blockSizeMinus1] = value;
if (_version?.IsVersioned ?? false)
{
_version.Inc();
}
}
}
public T[][] AsArray { get => _memoryBlocks; }
public T[][] AsArray => _memoryBlocks;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Add(T item)
@@ -667,6 +716,11 @@ namespace Recyclable.Collections
}
_longCount++;
if (_version?.IsVersioned ?? false)
{
_version.Inc();
}
}
public void AddRange(in T[] items)
@@ -715,6 +769,11 @@ namespace Recyclable.Collections
_nextItemIndex = itemsSpan.Length;
_longCount = targetCapacity;
_lastBlockWithData = targetBlockIndex;
if (_version?.IsVersioned ?? false)
{
_version.Inc();
}
}
public void AddRange(RecyclableList<T> items)
@@ -755,6 +814,11 @@ namespace Recyclable.Collections
_longCount = targetCapacity;
_nextItemBlockIndex = targetBlockIndex;
_lastBlockWithData = targetBlockIndex - (itemsSpan.Length > 0 ? 0 : 1);
if (_version?.IsVersioned ?? false)
{
_version.Inc();
}
}
public void AddRange(ReadOnlySpan<T> itemsSpan)
@@ -802,6 +866,11 @@ namespace Recyclable.Collections
_nextItemIndex = itemsSpan.Length;
_longCount = targetCapacity;
_lastBlockWithData = targetBlockIndex;
if (_version?.IsVersioned ?? false)
{
_version.Inc();
}
}
public void AddRange(RecyclableLongList<T> items)
@@ -874,6 +943,11 @@ namespace Recyclable.Collections
}
_longCount = targetCapacity;
if (_version?.IsVersioned ?? false)
{
_version.Inc();
}
}
public void AddRange(List<T> items)
@@ -921,6 +995,11 @@ namespace Recyclable.Collections
_nextItemBlockIndex = targetBlockIndex;
_longCount = targetCapacity;
_lastBlockWithData = targetBlockIndex - (_nextItemIndex > 0 ? 0 : 1);
if (_version?.IsVersioned ?? false)
{
_version.Inc();
}
}
public void AddRange(IList<T> items)
@@ -975,6 +1054,11 @@ namespace Recyclable.Collections
{
_defaultBlockArrayPool.Return(itemsBuffer, NeedsClearing);
}
if (_version?.IsVersioned ?? false)
{
_version.Inc();
}
}
}
@@ -983,40 +1067,41 @@ namespace Recyclable.Collections
if (source is RecyclableLongList<T> sourceRecyclableLongList)
{
AddRange(sourceRecyclableLongList);
return;
}
if (source is RecyclableList<T> sourceRecyclableList)
else if (source is RecyclableList<T> sourceRecyclableList)
{
AddRange(sourceRecyclableList);
return;
}
if (source is T[] sourceArray)
else if (source is T[] sourceArray)
{
AddRange(sourceArray);
return;
}
if (source is List<T> sourceList)
else if (source is List<T> sourceList)
{
AddRange(sourceList);
return;
}
if (source is IList<T> sourceIList)
else if (source is IList<T> sourceIList)
{
AddRange(sourceIList);
return;
}
if (source.TryGetNonEnumeratedCount(out var requiredAdditionalCapacity))
else if (source.TryGetNonEnumeratedCount(out var requiredAdditionalCapacity))
{
AddRangeWithKnownCount(source, requiredAdditionalCapacity);
return;
}
AddRangeEnumerated(source, growByCount);
if (_version?.IsVersioned ?? false)
{
_version.Inc();
}
}
else
{
AddRangeEnumerated(source, growByCount);
if (_version?.IsVersioned ?? false)
{
_version.Inc();
}
}
}
public void Clear()
@@ -1045,6 +1130,11 @@ namespace Recyclable.Collections
_nextItemIndex = 0;
_lastBlockWithData = RecyclableDefaults.ItemNotFoundIndex;
_longCount = 0;
if (_version?.IsVersioned ?? false)
{
_version.Inc();
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -1075,7 +1165,17 @@ namespace Recyclable.Collections
public void CopyTo(T[] array, int arrayIndex) => RecyclableLongList<T>.RecyclableLongListHelpers.CopyTo(_memoryBlocks, 0, _blockSize, _longCount, array, arrayIndex);
public IEnumerator<T> GetEnumerator() => RecyclableLongList<T>.RecyclableLongListHelpers.Enumerate(_memoryBlocks, _blockSize, LongCount).GetEnumerator();
public IEnumerator<T> GetEnumerator()
{
_version ??= new();
return new RecyclableLongListEnumerator<T>(this);
}
IEnumerator IEnumerable.GetEnumerator()
{
_version ??= new();
return new RecyclableLongListEnumerator<T>(this);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public int IndexOf(T item)
@@ -1188,6 +1288,11 @@ namespace Recyclable.Collections
#pragma warning restore CS8601
}
if (_version?.IsVersioned ?? false)
{
_version.Inc();
}
return true;
}
@@ -1217,6 +1322,11 @@ namespace Recyclable.Collections
}
_lastBlockWithData--;
if (_version?.IsVersioned ?? false)
{
_version.Inc();
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
@@ -1256,6 +1366,11 @@ namespace Recyclable.Collections
_memoryBlocks[_nextItemBlockIndex][_nextItemIndex] = default;
#pragma warning restore CS8601
}
if (_version?.IsVersioned ?? false)
{
_version.Inc();
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
@@ -1294,9 +1409,12 @@ namespace Recyclable.Collections
_memoryBlocks[_nextItemBlockIndex][_nextItemIndex] = default;
#pragma warning restore CS8601
}
}
IEnumerator IEnumerable.GetEnumerator() => RecyclableLongList<T>.RecyclableLongListHelpers.Enumerate(_memoryBlocks, _blockSize, LongCount).GetEnumerator();
if (_version?.IsVersioned ?? false)
{
_version.Inc();
}
}
~RecyclableLongList()
{
@@ -1315,6 +1433,13 @@ namespace Recyclable.Collections
_memoryBlocksPool.Return(_memoryBlocks, NeedsClearing);
}
if (_version != null)
{
//var toReturn = _version;
_version = null;
//RecyclableCollectionVersionPool.Shared.Return(toReturn);
}
GC.SuppressFinalize(this);
}
}
+23
View File
@@ -0,0 +1,23 @@
namespace Recyclable.Collections
{
internal static class ThrowHelper
{
/// <summary>
/// This is a helper method to throw <see cref="InvalidOperationException"/>, thrown when the collection was modified during enumeration.
/// </summary>
/// <exception cref="InvalidOperationException">Always</exception>
public static void ThrowListModifiedException() => throw new InvalidOperationException("Can't move to the next item, because the collection was modified. You must restart the enumeration by calling Reset(), if you want to enumerate the collection again");
/// <summary>
/// This is a helper method to throw <see cref="InvalidOperationException"/> with the given message.
/// </summary>
/// <exception cref="InvalidOperationException">Always</exception>
public static void ThrowInvalidOperationException(in string message) => throw new InvalidOperationException(message);
/// <summary>
/// This is a helper method to throw <see cref="ArgumentOutOfRangeException"/> with the given message.
/// </summary>
/// <exception cref="ArgumentOutOfRangeException">Always</exception>
public static void ThrowArgumentOutOfRangeException(in string argumentName, in string message) => throw new ArgumentOutOfRangeException(argumentName, message);
}
}