From 15cac80d2fe693ccc72bf8b09fa224268437651f Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Mon, 19 Jun 2023 22:02:11 +0200 Subject: [PATCH] RecyclableList.Enumerator performance improvements to match List's performance. Dirty state. --- .../Benchmarks/ForEach.cs | 45 +++++ .../RecyclableCollections.Benchmarks.cs | 73 +++---- .../POC/PocBenchmarkBase.cs | 2 +- Recyclable.Collections.Benchmarks/Program.cs | 17 +- .../Pools/RecyclableCollectionVersionPool.cs | 21 +- .../Pools/RecyclableList.EnumeratorPool.cs | 19 ++ .../RecyclableCollectionVersion.cs | 77 +++++-- .../RecyclableList.Enumerator.cs | 189 +++++++++++++----- .../RecyclableList.IList.cs | 10 +- Recyclable.Collections/RecyclableList.cs | 65 +++--- .../RecyclableLongList.Enumerator.cs | 149 ++++++++++++++ Recyclable.Collections/RecyclableLongList.cs | 185 ++++++++++++++--- Recyclable.Collections/ThrowHelper.cs | 23 +++ 13 files changed, 683 insertions(+), 192 deletions(-) create mode 100644 Recyclable.Collections.Benchmarks/Benchmarks/ForEach.cs create mode 100644 Recyclable.Collections/Pools/RecyclableList.EnumeratorPool.cs create mode 100644 Recyclable.Collections/RecyclableLongList.Enumerator.cs create mode 100644 Recyclable.Collections/ThrowHelper.cs diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/ForEach.cs b/Recyclable.Collections.Benchmarks/Benchmarks/ForEach.cs new file mode 100644 index 0000000..1b9254a --- /dev/null +++ b/Recyclable.Collections.Benchmarks/Benchmarks/ForEach.cs @@ -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); + } + } + } +} diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/RecyclableCollections.Benchmarks.cs b/Recyclable.Collections.Benchmarks/Benchmarks/RecyclableCollections.Benchmarks.cs index 901ac8b..1b341e0 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/RecyclableCollections.Benchmarks.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/RecyclableCollections.Benchmarks.cs @@ -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; } diff --git a/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs b/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs index b3ba2ad..38483a3 100644 --- a/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs +++ b/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs @@ -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)] diff --git a/Recyclable.Collections.Benchmarks/Program.cs b/Recyclable.Collections.Benchmarks/Program.cs index 2608901..14f5bbd 100644 --- a/Recyclable.Collections.Benchmarks/Program.cs +++ b/Recyclable.Collections.Benchmarks/Program.cs @@ -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(); } } diff --git a/Recyclable.Collections/Pools/RecyclableCollectionVersionPool.cs b/Recyclable.Collections/Pools/RecyclableCollectionVersionPool.cs index cba1b81..b87564f 100644 --- a/Recyclable.Collections/Pools/RecyclableCollectionVersionPool.cs +++ b/Recyclable.Collections/Pools/RecyclableCollectionVersionPool.cs @@ -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 _pool = new DefaultObjectPool(new DefaultPooledObjectPolicy()); + // private readonly ObjectPool _pool = new DefaultObjectPool(new DefaultPooledObjectPolicy()); - 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); + //} } diff --git a/Recyclable.Collections/Pools/RecyclableList.EnumeratorPool.cs b/Recyclable.Collections/Pools/RecyclableList.EnumeratorPool.cs new file mode 100644 index 0000000..b094250 --- /dev/null +++ b/Recyclable.Collections/Pools/RecyclableList.EnumeratorPool.cs @@ -0,0 +1,19 @@ +using Microsoft.Extensions.ObjectPool; + +namespace Recyclable.Collections +{ + public partial class RecyclableList + { + //internal class RecyclableListEnumeratorPool + //{ + // private static readonly RecyclableListEnumeratorPool _shared = new(); + // public static RecyclableListEnumeratorPool Shared => _shared; + + // private readonly ObjectPool _pool = new DefaultObjectPool(new DefaultPooledObjectPolicy()); + + // public RecyclableListEnumerator Get(RecyclableList list) => _pool.Get().Reset(this, list); + + // public void Return(RecyclableListEnumerator version) => _pool.Return(version); + //} + } +} \ No newline at end of file diff --git a/Recyclable.Collections/RecyclableCollectionVersion.cs b/Recyclable.Collections/RecyclableCollectionVersion.cs index 4d8781c..829e1a1 100644 --- a/Recyclable.Collections/RecyclableCollectionVersion.cs +++ b/Recyclable.Collections/RecyclableCollectionVersion.cs @@ -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; } } diff --git a/Recyclable.Collections/RecyclableList.Enumerator.cs b/Recyclable.Collections/RecyclableList.Enumerator.cs index 110d0c0..89a9be0 100644 --- a/Recyclable.Collections/RecyclableList.Enumerator.cs +++ b/Recyclable.Collections/RecyclableList.Enumerator.cs @@ -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 : IEnumerator + public partial class RecyclableList { - 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? _list; - private RecyclableCollectionVersion? _listVersion; - private ulong _version; - - private T? _current; - - public RecyclableListEnumerator(RecyclableList list) + public struct Enumerator : IEnumerator, IEnumerator { - _list = list; - _listVersion = list._version; - _listVersion!.AddEnumerator(); - _version = _listVersion!.Version; - } +#nullable disable + private readonly RecyclableList _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 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 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 } diff --git a/Recyclable.Collections/RecyclableList.IList.cs b/Recyclable.Collections/RecyclableList.IList.cs index 158421a..f54d8b8 100644 --- a/Recyclable.Collections/RecyclableList.IList.cs +++ b/Recyclable.Collections/RecyclableList.IList.cs @@ -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.")}"); } } diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 0aa3426..e823ef4 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -5,13 +5,13 @@ using System.Runtime.CompilerServices; namespace Recyclable.Collections { - public partial class RecyclableList : IList, IReadOnlyList, IDisposable + [Serializable] + public sealed partial class RecyclableList : IList, IReadOnlyList, IList, IDisposable { private static readonly ArrayPool _arrayPool = ArrayPool.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(RecyclableList 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)}, equal {int.MaxValue}, equal {int.MaxValue}. Please consider using {nameof(RecyclableLongList)}, instead"); + ThrowHelper.ThrowArgumentOutOfRangeException(nameof(items), $"The number of items exceeds the maximum capacity of {nameof(RecyclableList)}, equal {int.MaxValue}, equal {int.MaxValue}. Please consider using {nameof(RecyclableLongList)}, 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)}, equal {int.MaxValue}. Please consider using {nameof(RecyclableLongList)}, instead"); + ThrowHelper.ThrowArgumentOutOfRangeException(nameof(items), $"The total number of items in source and target table exceeds the maximum capacity of {nameof(RecyclableList)}, equal {int.MaxValue}. Please consider using {nameof(RecyclableLongList)}, 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 GetEnumerator() - { - _version ??= RecyclableCollectionVersionPool.Shared.Get(); - return new RecyclableListEnumerator(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(this); - } + //[MethodImpl(MethodImplOptions.AggressiveInlining)] + IEnumerator IEnumerable.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); diff --git a/Recyclable.Collections/RecyclableLongList.Enumerator.cs b/Recyclable.Collections/RecyclableLongList.Enumerator.cs new file mode 100644 index 0000000..7f1ee37 --- /dev/null +++ b/Recyclable.Collections/RecyclableLongList.Enumerator.cs @@ -0,0 +1,149 @@ +using System.Collections; + +namespace Recyclable.Collections +{ + public sealed class RecyclableLongListEnumerator : IEnumerator + { + 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? _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 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 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; + } + } +} diff --git a/Recyclable.Collections/RecyclableLongList.cs b/Recyclable.Collections/RecyclableLongList.cs index d75b843..db6664c 100644 --- a/Recyclable.Collections/RecyclableLongList.cs +++ b/Recyclable.Collections/RecyclableLongList.cs @@ -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 list, long destinationItemIndex) @@ -631,16 +664,32 @@ namespace Recyclable.Collections public T this[long index] { get => _memoryBlocks[index >> _blockSizePow2BitShift][index & _blockSizeMinus1]; - set => new Span(_memoryBlocks[checked((int)(index >> _blockSizePow2BitShift))])[checked((int)(index & _blockSizeMinus1))] = value; + set + { + new Span(_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(_memoryBlocks[index >> _blockSizePow2BitShift])[index & _blockSizeMinus1] = value; + set + { + new Span(_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 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 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 items) @@ -874,6 +943,11 @@ namespace Recyclable.Collections } _longCount = targetCapacity; + + if (_version?.IsVersioned ?? false) + { + _version.Inc(); + } } public void AddRange(List 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 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 sourceRecyclableLongList) { AddRange(sourceRecyclableLongList); - return; } - - if (source is RecyclableList sourceRecyclableList) + else if (source is RecyclableList sourceRecyclableList) { AddRange(sourceRecyclableList); - return; } - - if (source is T[] sourceArray) + else if (source is T[] sourceArray) { AddRange(sourceArray); - return; } - - if (source is List sourceList) + else if (source is List sourceList) { AddRange(sourceList); - return; } - - if (source is IList sourceIList) + else if (source is IList 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.RecyclableLongListHelpers.CopyTo(_memoryBlocks, 0, _blockSize, _longCount, array, arrayIndex); - public IEnumerator GetEnumerator() => RecyclableLongList.RecyclableLongListHelpers.Enumerate(_memoryBlocks, _blockSize, LongCount).GetEnumerator(); + public IEnumerator GetEnumerator() + { + _version ??= new(); + return new RecyclableLongListEnumerator(this); + } + + IEnumerator IEnumerable.GetEnumerator() + { + _version ??= new(); + return new RecyclableLongListEnumerator(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.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); } } diff --git a/Recyclable.Collections/ThrowHelper.cs b/Recyclable.Collections/ThrowHelper.cs new file mode 100644 index 0000000..d19b3b2 --- /dev/null +++ b/Recyclable.Collections/ThrowHelper.cs @@ -0,0 +1,23 @@ +namespace Recyclable.Collections +{ + internal static class ThrowHelper + { + /// + /// This is a helper method to throw , thrown when the collection was modified during enumeration. + /// + /// Always + 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"); + + /// + /// This is a helper method to throw with the given message. + /// + /// Always + public static void ThrowInvalidOperationException(in string message) => throw new InvalidOperationException(message); + + /// + /// This is a helper method to throw with the given message. + /// + /// Always + public static void ThrowArgumentOutOfRangeException(in string argumentName, in string message) => throw new ArgumentOutOfRangeException(argumentName, message); + } +}