diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/ForEach.cs b/Recyclable.Collections.Benchmarks/Benchmarks/ForEach.cs index 1b9254a..bba42e9 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/ForEach.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/ForEach.cs @@ -10,6 +10,14 @@ } } + public void Array_VersionedForEach() + { + foreach (var item in TestObjects) + { + DoNothing(item); + } + } + public void List_ForEach() { foreach (var item in TestObjectsAsList) @@ -18,6 +26,14 @@ } } + public void List_VersionedForEach() + { + foreach (var item in TestObjectsAsList) + { + DoNothing(item); + } + } + public void PooledList_ForEach() { foreach (var item in TestObjectsAsPooledList) @@ -26,6 +42,14 @@ } } + public void PooledList_VersionedForEach() + { + foreach (var item in TestObjectsAsPooledList) + { + DoNothing(item); + } + } + public void RecyclableList_ForEach() { foreach (var item in TestObjectsAsRecyclableList) @@ -34,6 +58,14 @@ } } + public void RecyclableList_VersionedForEach() + { + foreach (var item in (IVersionedList)TestObjectsAsRecyclableList) + { + DoNothing(item); + } + } + public void RecyclableLongList_ForEach() { foreach (var item in TestObjectsAsRecyclableLongList) @@ -41,5 +73,13 @@ DoNothing(item); } } + + public void RecyclableLongList_VersionedForEach() + { + foreach (var item in (IVersionedLongList)TestObjectsAsRecyclableLongList) + { + DoNothing(item); + } + } } } diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/RecyclableCollections.Benchmarks.cs b/Recyclable.Collections.Benchmarks/Benchmarks/RecyclableCollections.Benchmarks.cs index 1b341e0..1317951 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/RecyclableCollections.Benchmarks.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/RecyclableCollections.Benchmarks.cs @@ -9,7 +9,7 @@ namespace Recyclable.Collections.Benchmarks AddRangeWhenSourceIsIList, AddRangeWhenSourceIsList, AddRangeWhenSourceIsSameType, Contains_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, + SetItem, VersionedForEach } public partial class RecyclableCollectionsBenchmarks : RecyclableBenchmarkBase @@ -23,47 +23,48 @@ namespace Recyclable.Collections.Benchmarks [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.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 + 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, + RecyclableCollectionsBenchmarkType.VersionedForEach )] 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/IVersionedList.cs b/Recyclable.Collections/IVersionedList.cs new file mode 100644 index 0000000..b62f75e --- /dev/null +++ b/Recyclable.Collections/IVersionedList.cs @@ -0,0 +1,7 @@ +namespace Recyclable.Collections +{ + public interface IVersionedList : IEnumerable + { + new RecyclableList.VersionedEnumerator GetEnumerator(); + } +} diff --git a/Recyclable.Collections/RecyclableDefaults.cs b/Recyclable.Collections/RecyclableDefaults.cs index 1fa29b9..5e5898a 100644 --- a/Recyclable.Collections/RecyclableDefaults.cs +++ b/Recyclable.Collections/RecyclableDefaults.cs @@ -2,6 +2,7 @@ { public static class RecyclableDefaults { + public const int BlockIndexLimit = int.MaxValue; public const int BlockSize = 16_384; public const int Capacity = 1; public const long MinItemsCountForParallelization = 850_000; diff --git a/Recyclable.Collections/RecyclableList.Enumerator.cs b/Recyclable.Collections/RecyclableList.Enumerator.cs index 7dc6adc..eb3a920 100644 --- a/Recyclable.Collections/RecyclableList.Enumerator.cs +++ b/Recyclable.Collections/RecyclableList.Enumerator.cs @@ -1,209 +1,50 @@ using System.Collections; -using System.Diagnostics.Contracts; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace Recyclable.Collections { public partial class RecyclableList { - [StructLayout(LayoutKind.Auto)] + [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct Enumerator : IEnumerator { - private int _currentIndex; - private readonly ulong _enumeratorVersion; - private readonly RecyclableList _list; + private static readonly bool _isReferenceType = !typeof(T).IsValueType; - // & WAS SLOWER - // private bool _isInValidState = false; - // private bool _endOfCollectionReached; - // private T _current; + private int _currentItemIndex; + private readonly RecyclableList _list; #nullable disable // [Pure] - public readonly T Current - { - get - { - if (_enumeratorVersion != _list._version) - { - throw new InvalidOperationException(); - // & WAS SLOWER - // ThrowHelper.ThrowListModifiedException(); - } - - if (_currentIndex == 0 || _currentIndex > _list._count) - { - throw new IndexOutOfRangeException(); - // & WAS SLOWER - // ThrowHelper.ThrowCollectionNotInitialized();// new IndexOutOfRangeException();// _current; - } - - Contract.EndContractBlock(); - - return _list._memoryBlock[_currentIndex - 1];// _current; - } - } - + public readonly T Current => _list._memoryBlock[_currentItemIndex - 1]; // [Pure] - object IEnumerator.Current - { - get - { - if (_enumeratorVersion != _list._version) - { - throw new InvalidOperationException(); - // & WAS SLOWER - // ThrowHelper.ThrowListModifiedException(); - } + readonly object IEnumerator.Current => _list._memoryBlock[_currentItemIndex - 1]; +#nullable restore - if (_currentIndex == 0 || _currentIndex > _list._count) - { - throw new IndexOutOfRangeException(); - // & WAS SLOWER - // ThrowHelper.ThrowCollectionNotInitialized(); - } - - Contract.EndContractBlock(); - - return _list._memoryBlock[_currentIndex - 1]; - - // & WAS SLOWER - // return _current; - } - } - - // & WAS SLOWER - // private readonly int _listCount; - // private RecyclableCollectionVersion _listVersion; - // private readonly T[] _memoryBlock; - // private bool _isVersionChanged; - // private void VersionChanged() => _isVersionChanged = true; - // internal RecyclableListEnumeratorPool? _pool; - // internal bool _returned; - // private readonly int _listCountMinus1; - - // & WAS SLOWER - // [MethodImpl(MethodImplOptions.AggressiveInlining)] - // [MethodImpl(MethodImplOptions.NoInlining)] public bool MoveNext() { - // & WAS SLOWER - // var list = _list; - if (_enumeratorVersion != _list._version) + if (_currentItemIndex < _list._count) { - throw new InvalidOperationException(); - // & WAS SLOWER - // ThrowHelper.ThrowListModifiedException(); - } - - Contract.EndContractBlock(); - - // & WAS SLOWER - // if (_currentIndex == 0) - // { - // _isInValidState = true; - // _currentIndex++; - // return true; - // } - // else - if (_currentIndex < _list._count) - { - // & WAS SLOWER - // _current = _list._memoryBlock[_currentIndex]; - _currentIndex++; + _currentItemIndex++; return true; } - // & WAS SLOWER - // else if (_currentIndex == _list._count) - // { - // & WAS SLOWER - // This isn't needed in most use-cases, because the enumerator will get disposed, releasing the reference, if any. - // _current = _defaultValue; - // _currentIndex++; - // return false; - // } - // else if (!_endOfCollectionReached) - // { - // // & WAS SLOWER - // // This isn't needed in most use-cases, because the enumerator will get disposed, releasing the reference, if any. - // // _current = _defaultValue; - // // _currentIndex = int.MinValue; - // _endOfCollectionReached = true; - // return false; - // } - - // & WAS SLOWER - // else if (!_endOfCollectionReached) - // { - // _current = default; - // _endOfCollectionReached = true; - // return false; - // } - - // & WAS SLOWER - // _current = default; - // _currentIndex = _list._count + 1; return false; } public Enumerator(RecyclableList list) { - _currentIndex = 0; + _currentItemIndex = 0; _list = list; - _enumeratorVersion = list._version; - - // & WAS SLOWER - // _current = default; - // _endOfCollectionReached = false; - // _listCount = list._count; - // _memoryBlock = list._memoryBlock; - // _isVersionChanged = false; - // _listVersion = list.Version; - // _listVersion.AddEnumerator(); - // _enumeratorVersion = list._version.Version; - // list._version.RegisterHandler(VersionChanged); - // _enumeratorVersion = _listVersion.Version; - // _endOfCollectionReached = false; - // _listCountMinus1 = list._count - 1; } public readonly void Dispose() { - // & WAS SLOWER - //if (_listVersion != null) - //{ - //_list._version.RemoveEnumerator(); - //_listVersion.RemoveEnumerator(this); - //} - - // & WAS SLOWER - // if (_pool != null) - //{ - // if (!_returned) - // { - // _returned = true; - // _pool?.Return(this); - // } - //} - //else - //{ - //_memoryBlock = null; - //GC.SuppressFinalize(this); - //} } void IEnumerator.Reset() { - _currentIndex = 0; - - // & WAS SLOWER - // _endOfCollectionReached = false; - // _isInValidState = false; - // _current = default; + _currentItemIndex = 0; } } } -#nullable restore } diff --git a/Recyclable.Collections/RecyclableList.VersionedEnumerator.cs b/Recyclable.Collections/RecyclableList.VersionedEnumerator.cs new file mode 100644 index 0000000..d36f548 --- /dev/null +++ b/Recyclable.Collections/RecyclableList.VersionedEnumerator.cs @@ -0,0 +1,65 @@ +using System.Collections; +using System.Diagnostics.Contracts; +using System.Runtime.InteropServices; + +namespace Recyclable.Collections +{ + public partial class RecyclableList : IVersionedList + { + VersionedEnumerator IVersionedList.GetEnumerator() => new(this); + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct VersionedEnumerator : IEnumerator + { + private int _currentItemIndex; + private readonly ulong _enumeratorVersion; + private readonly RecyclableList _list; + +#nullable disable + // [Pure] + public readonly T Current => _enumeratorVersion == _list._version ? _list._memoryBlock[_currentItemIndex - 1] : throw new InvalidOperationException(); + // [Pure] + readonly object IEnumerator.Current => _enumeratorVersion == _list._version ? _list._memoryBlock[_currentItemIndex - 1] : throw new InvalidOperationException(); +#nullable restore + + public bool MoveNext() + { + if (_enumeratorVersion != _list._version) + { + throw new InvalidOperationException(); + } + + Contract.EndContractBlock(); + + if (_currentItemIndex < _list._count) + { + _currentItemIndex++; + return true; + } + + return false; + } + + public VersionedEnumerator(RecyclableList list) + { + _currentItemIndex = 0; + _list = list; + _enumeratorVersion = list._version; + } + + public readonly void Dispose() + { + } + + void IEnumerator.Reset() + { + if (_enumeratorVersion != _list._version) + { + throw new InvalidOperationException(); + } + + _currentItemIndex = 0; + } + } + } +} diff --git a/Recyclable.CollectionsTests/RecyclableListTests.cs b/Recyclable.CollectionsTests/RecyclableListTests.cs index d47ca01..1cda68c 100644 --- a/Recyclable.CollectionsTests/RecyclableListTests.cs +++ b/Recyclable.CollectionsTests/RecyclableListTests.cs @@ -336,7 +336,6 @@ namespace Recyclable.CollectionsTests list.CopyTo(copiedItems, 0); // Validate - _ = copiedItems.Length.Should().Be((int)itemsCount); _ = copiedItems.Should().ContainInConsecutiveOrder(testData); } @@ -551,5 +550,23 @@ namespace Recyclable.CollectionsTests _ = list.Should().ContainInConsecutiveOrder(testData.Take((int)(itemsCount - deleted))); } } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))] + public void VersionedEnumerateShouldYieldAllItemsInCorrectOrder(string testCase, IEnumerable testData, long itemsCount) + { + // Prepare + using var list = new RecyclableList(testData); + + // Act + var yieldedItems = new List((int)itemsCount); + foreach (var item in (IVersionedList)list) + { + yieldedItems.Add(item); + } + + // Validate + _ = yieldedItems.Should().ContainInConsecutiveOrder(testData); + } } } \ No newline at end of file