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 public enum RecyclableCollectionsBenchmarkType
{ {
Add_WithCapacity, Add, AddRange_WithCapacity, AddRangeWhenSourceIsArray, AddRangeWhenSourceIsIEnumerable, Unknown, Add_WithCapacity, Add, AddRange_WithCapacity, AddRangeWhenSourceIsArray, AddRangeWhenSourceIsIEnumerable,
AddRangeWhenSourceIsIList, AddRangeWhenSourceIsList, AddRangeWhenSourceIsSameType, Contains_FirstItems, 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, IndexOf_LastItems, LongCount, Remove_FirstItems, Remove_LastItems, RemoveAt_FirstItems, RemoveAt_LastItems,
SetItem, SetItem,
} }
@@ -17,52 +17,53 @@ namespace Recyclable.Collections.Benchmarks
public RecyclableCollectionsBenchmarks() : base() public RecyclableCollectionsBenchmarks() : base()
{ {
TestObjectCount = 1; TestObjectCount = 1;
BaseDataType = RecyclableCollectionsBenchmarkSource.List; //BaseDataType = RecyclableCollectionsBenchmarkSource.List;
DataType = RecyclableCollectionsBenchmarkSource.RecyclableLongList; DataType = RecyclableCollectionsBenchmarkSource.RecyclableList;
} }
[Params [Params
( (
RecyclableCollectionsBenchmarkType.Add_WithCapacity, //RecyclableCollectionsBenchmarkType.Add_WithCapacity,
RecyclableCollectionsBenchmarkType.Add, //RecyclableCollectionsBenchmarkType.Add,
RecyclableCollectionsBenchmarkType.AddRange_WithCapacity, //RecyclableCollectionsBenchmarkType.AddRange_WithCapacity,
RecyclableCollectionsBenchmarkType.AddRangeWhenSourceIsArray, //RecyclableCollectionsBenchmarkType.AddRangeWhenSourceIsArray,
RecyclableCollectionsBenchmarkType.AddRangeWhenSourceIsIEnumerable, //RecyclableCollectionsBenchmarkType.AddRangeWhenSourceIsIEnumerable,
RecyclableCollectionsBenchmarkType.AddRangeWhenSourceIsIList, //RecyclableCollectionsBenchmarkType.AddRangeWhenSourceIsIList,
RecyclableCollectionsBenchmarkType.AddRangeWhenSourceIsList, //RecyclableCollectionsBenchmarkType.AddRangeWhenSourceIsList,
RecyclableCollectionsBenchmarkType.AddRangeWhenSourceIsSameType, //RecyclableCollectionsBenchmarkType.AddRangeWhenSourceIsSameType,
RecyclableCollectionsBenchmarkType.Contains_FirstItems, //RecyclableCollectionsBenchmarkType.Contains_FirstItems,
RecyclableCollectionsBenchmarkType.Contains_LastItems, //RecyclableCollectionsBenchmarkType.Contains_LastItems,
RecyclableCollectionsBenchmarkType.Count, //RecyclableCollectionsBenchmarkType.Count,
RecyclableCollectionsBenchmarkType.Create_WithCapacity, //RecyclableCollectionsBenchmarkType.Create_WithCapacity,
RecyclableCollectionsBenchmarkType.Create, //RecyclableCollectionsBenchmarkType.Create,
RecyclableCollectionsBenchmarkType.GetItem, RecyclableCollectionsBenchmarkType.ForEach
RecyclableCollectionsBenchmarkType.IndexOf_BestAndWorstCases, //RecyclableCollectionsBenchmarkType.GetItem,
RecyclableCollectionsBenchmarkType.IndexOf_FirstItems, //RecyclableCollectionsBenchmarkType.IndexOf_BestAndWorstCases,
RecyclableCollectionsBenchmarkType.IndexOf_LastItems, //RecyclableCollectionsBenchmarkType.IndexOf_FirstItems,
RecyclableCollectionsBenchmarkType.LongCount, //RecyclableCollectionsBenchmarkType.IndexOf_LastItems,
RecyclableCollectionsBenchmarkType.Remove_FirstItems, //RecyclableCollectionsBenchmarkType.LongCount,
RecyclableCollectionsBenchmarkType.Remove_LastItems, //RecyclableCollectionsBenchmarkType.Remove_FirstItems,
RecyclableCollectionsBenchmarkType.RemoveAt_FirstItems, //RecyclableCollectionsBenchmarkType.Remove_LastItems,
RecyclableCollectionsBenchmarkType.RemoveAt_LastItems, //RecyclableCollectionsBenchmarkType.RemoveAt_FirstItems,
RecyclableCollectionsBenchmarkType.SetItem //RecyclableCollectionsBenchmarkType.RemoveAt_LastItems,
//RecyclableCollectionsBenchmarkType.SetItem
)] )]
public RecyclableCollectionsBenchmarkType TestCase { get; set; } = RecyclableCollectionsBenchmarkType.IndexOf_BestAndWorstCases; public RecyclableCollectionsBenchmarkType TestCase { get; set; } = RecyclableCollectionsBenchmarkType.IndexOf_BestAndWorstCases;
[Params //[Params
( //(
RecyclableCollectionsBenchmarkSource.List // RecyclableCollectionsBenchmarkSource.List
)] //)]
public override RecyclableCollectionsBenchmarkSource BaseDataType { get => base.BaseDataType; set => base.BaseDataType = value; } public override RecyclableCollectionsBenchmarkSource BaseDataType { get => base.BaseDataType; set => base.BaseDataType = value; }
[Params [Params
( (
RecyclableCollectionsBenchmarkSource.Array, //RecyclableCollectionsBenchmarkSource.Array,
RecyclableCollectionsBenchmarkSource.List, //RecyclableCollectionsBenchmarkSource.List,
RecyclableCollectionsBenchmarkSource.PooledList, //RecyclableCollectionsBenchmarkSource.PooledList,
RecyclableCollectionsBenchmarkSource.RecyclableList, RecyclableCollectionsBenchmarkSource.RecyclableList
RecyclableCollectionsBenchmarkSource.RecyclableLongList //RecyclableCollectionsBenchmarkSource.RecyclableLongList
)] )]
public override RecyclableCollectionsBenchmarkSource DataType { get => base.DataType; set => base.DataType = value; } 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(1, 10, 1024, 10240, RecyclableDefaults.MinPooledArrayLength, RecyclableDefaults.MinItemsCountForParallelization)]
// [Params(RecyclableDefaults.MinItemsCountForParallelization * 3)] // [Params(RecyclableDefaults.MinItemsCountForParallelization * 3)]
// [Params(1_048_576)] // [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); 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)] // [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 *** // *** Template ***
// **************** // ****************
//var benchmark = new RecyclableCollectionsBenchmarks(); var benchmark = new RecyclableCollectionsBenchmarks();
//benchmark.Setup(); benchmark.Setup();
//foreach (var _ in Enumerable.Range(1, 1000)) //foreach (var _ in Enumerable.Range(1, 1000))
//{ //{
// benchmark.RecyclableLongList_Remove_FirstItems(); benchmark.List_ForEach();
// benchmark.RecyclableLongList_RemoveAt_LastItems(); benchmark.RecyclableList_ForEach();
// benchmark.List_RemoveAt_LastItems();
//} //}
//benchmark.Cleanup(); benchmark.Cleanup();
} }
static void RunPocBenchmarks() static void RunPocBenchmarks()
@@ -53,8 +52,8 @@ public static class Program
static void Main(string[] args) static void Main(string[] args)
{ {
RunRecyclableCollectionsBenchmarks(); RunRecyclableCollectionsBenchmarks();
// RunPocBenchmarks(); //RunPocBenchmarks();
// RunSelectedBenchmarks(); //RunSelectedBenchmarks();
// RunAssemblyBenchmarks(); //RunAssemblyBenchmarks();
} }
} }
@@ -1,20 +1,15 @@
using Microsoft.Extensions.ObjectPool; using Microsoft.Extensions.ObjectPool;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Recyclable.Collections.Pools namespace Recyclable.Collections.Pools
{ {
public class RecyclableCollectionVersionPool //public class RecyclableCollectionVersionPool
{ //{
private static readonly RecyclableCollectionVersionPool _shared = new(); // private static readonly RecyclableCollectionVersionPool _shared = new();
public static RecyclableCollectionVersionPool Shared => _shared; // 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 RecyclableCollectionVersion Get() => _pool.Get().Reset();
public void Return(RecyclableCollectionVersion version) => _pool.Return(version); // 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 namespace Recyclable.Collections
{ {
public delegate void VersionChangedEventHandler();
public sealed class RecyclableCollectionVersion public sealed class RecyclableCollectionVersion
{ {
private ulong _enumeratorCount; //private ulong _enumeratorCount;
public ulong Version;
//private ulong _version;
//public ulong Version
//{
// get => _version;
// set
// {
// _version = value;
// _versionChanged?.Invoke(this, EventArgs.Empty);
// }
//}
//public uint Version { get; set; }
public bool IsVersioned; public bool IsVersioned;
[MethodImpl(MethodImplOptions.AggressiveInlining)] private VersionChangedEventHandler? _versionChanged;
public void Inc() => _ = unchecked(Version++); public event VersionChangedEventHandler VersionChanged
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddEnumerator()
{ {
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)] [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)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public RecyclableCollectionVersion Reset() public RecyclableCollectionVersion Reset()
{ {
IsVersioned = false; IsVersioned = false;
Version = 0; //Version = 0;
_enumeratorCount = 0; //_enumeratorCount = 0;
return this; return this;
} }
} }
@@ -1,79 +1,166 @@
using System.Collections; using System.Collections;
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
using System.Runtime.ConstrainedExecution;
namespace Recyclable.Collections 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; public struct Enumerator : IEnumerator<T>, IEnumerator
private RecyclableList<T>? _list;
private RecyclableCollectionVersion? _listVersion;
private ulong _version;
private T? _current;
public RecyclableListEnumerator(RecyclableList<T> list)
{ {
_list = list; #nullable disable
_listVersion = list._version; private readonly RecyclableList<T> _list;
_listVersion!.AddEnumerator(); private int _currentIndex;
_version = _listVersion!.Version; 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"); //[Pure]
object? IEnumerator.Current => _current; //private readonly int _listCount;
//private readonly int _listCountMinus1;
//private readonly T[] _memoryBlock;
//private bool _endOfCollectionReached;
public void Dispose() //private RecyclableCollectionVersion _listVersion;
{ //private ulong _enumeratorVersion;
if (_listVersion != null) //private bool _isVersionChanged;
//private void VersionChanged() => _isVersionChanged = true;
//internal RecyclableListEnumeratorPool? _pool;
//internal bool _returned;
//public RecyclableListEnumerator()
//{
//}
//[Pure]
internal Enumerator(RecyclableList<T> list)
{ {
_listVersion.RemoveEnumerator(); _current = default;
_listVersion = null; _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); //[MethodImpl(MethodImplOptions.AggressiveInlining)]
} public bool MoveNext()
public bool MoveNext()
{
if (_list != null)
{ {
if (_list._version!.Version != _version) var list = _list;
{ //if (_isVersionChanged)
ThrowListModifiedException(); //{
return false; // ThrowHelper.ThrowListModifiedException();
} // return false;
else if (_currentIndex < _list.Count - 1) //}
//else
if (_currentIndex < list._count)
{ {
_current = list._memoryBlock[_currentIndex];
_currentIndex++; _currentIndex++;
_current = _list[_currentIndex];
return true; return true;
} }
else
{ //else if (!_endOfCollectionReached)
return false; //{
} // _endOfCollectionReached = true;
} // return false;
else //}
{
_current = default;
return false; return false;
} }
}
public void Reset() #nullable restore
{ public T Current => _current;
_currentIndex = RecyclableDefaults.ItemNotFoundIndex; object IEnumerator.Current => _current;
_current = default; #nullable disable
if (_list != null)
//[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 IsFixedSize => false;
public bool IsSynchronized => false; public bool IsSynchronized => false;
[NonSerialized]
private object? _syncRoot; private object? _syncRoot;
public object SyncRoot public object SyncRoot
{ {
get get
@@ -39,7 +41,7 @@ namespace Recyclable.Collections
} }
else 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 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; return RecyclableDefaults.ItemNotFoundIndex;
} }
} }
@@ -91,7 +93,7 @@ namespace Recyclable.Collections
} }
else 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 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 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 ArrayPool<T> _arrayPool = ArrayPool<T>.Create();
private static readonly bool _defaultIsNull = default(T) == null; private static readonly bool _defaultIsNull = default(T) == null;
protected static readonly bool NeedsClearing = !typeof(T).IsValueType; 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); public static explicit operator ReadOnlySpan<T>(RecyclableList<T> source) => new(source._memoryBlock, 0, source.Count);
protected T[] _memoryBlock; protected T[] _memoryBlock;
@@ -224,23 +224,23 @@ namespace Recyclable.Collections
} }
protected int _count; protected int _count;
public int Count public int Count => _count;
{ //{
[MethodImpl(MethodImplOptions.AggressiveInlining)] // [MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _count; // get => _count;
[MethodImpl(MethodImplOptions.AggressiveInlining)] // [MethodImpl(MethodImplOptions.AggressiveInlining)]
// TODO: Clear items if we're decreasing the size of the list, if T is reference type // // TODO: Clear items if we're decreasing the size of the list, if T is reference type
set // set
{ // {
_count = value; // _count = value;
if (_version?.IsVersioned ?? false) // if (_version?.IsVersioned ?? false)
{ // {
_version.Inc(); // _version.Inc();
} // }
} // }
} //}
public bool IsReadOnly => false; public bool IsReadOnly => false;
@@ -397,12 +397,12 @@ namespace Recyclable.Collections
var sourceItemsCount = items.LongCount; var sourceItemsCount = items.LongCount;
if (sourceItemsCount > int.MaxValue) 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) 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; int targetCapacity = _count + (int)sourceItemsCount;
@@ -571,7 +571,7 @@ namespace Recyclable.Collections
{ {
if (index >= _count) 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--; _count--;
@@ -593,19 +593,16 @@ namespace Recyclable.Collections
} }
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] public Enumerator GetEnumerator() => new Enumerator(this);
public IEnumerator<T> GetEnumerator() //[MethodImpl(MethodImplOptions.AggressiveInlining)]
{ IEnumerator IEnumerable.GetEnumerator() =>
_version ??= RecyclableCollectionVersionPool.Shared.Get(); //_version ??= new(); // RecyclableCollectionVersionPool.Shared.Get();
return new RecyclableListEnumerator<T>(this); new Enumerator(this);//return RecyclableListEnumeratorPool.Shared.Get(this);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)] //[MethodImpl(MethodImplOptions.AggressiveInlining)]
IEnumerator IEnumerable.GetEnumerator() IEnumerator<T> IEnumerable<T>.GetEnumerator() =>
{ //_version ??= new(); // RecyclableCollectionVersionPool.Shared.Get();
_version ??= RecyclableCollectionVersionPool.Shared.Get(); new Enumerator(this);//return RecyclableListEnumeratorPool.Shared.Get(this);
return new RecyclableListEnumerator<T>(this);
}
public void Dispose() public void Dispose()
{ {
@@ -620,9 +617,9 @@ namespace Recyclable.Collections
if (_version != null) if (_version != null)
{ {
var toReturn = _version; //var toReturn = _version;
_version = null; _version = null;
RecyclableCollectionVersionPool.Shared.Return(toReturn); //RecyclableCollectionVersionPool.Shared.Return(toReturn);
} }
GC.SuppressFinalize(this); 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 int _blockSize;
internal byte _blockSizePow2BitShift; internal byte _blockSizePow2BitShift;
internal int _blockSizeMinus1; internal int _blockSizeMinus1;
private int _nextItemBlockIndex; internal int _nextItemBlockIndex;
internal int _nextItemIndex; internal int _nextItemIndex;
private int _reservedBlockCount; private int _reservedBlockCount;
@@ -36,12 +36,34 @@ namespace Recyclable.Collections
public long Capacity public long Capacity
{ {
get => _capacity; get => _capacity;
protected set => _capacity = value; protected set
{
_capacity = value;
if (_version?.IsVersioned ?? false)
{
_version.Inc();
}
}
} }
public int Count => checked((int)_longCount); public int Count
public bool IsReadOnly { get; } {
private int _lastBlockWithData = RecyclableDefaults.ItemNotFoundIndex; 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; public int LastBlockWithData => _lastBlockWithData;
internal long _longCount; internal long _longCount;
@@ -49,7 +71,15 @@ namespace Recyclable.Collections
public long LongCount public long LongCount
{ {
get => _longCount; get => _longCount;
set => _longCount = value; set
{
_longCount = value;
if (_version?.IsVersioned ?? false)
{
_version.Inc();
}
}
} }
public int ReservedBlockCount => _reservedBlockCount; public int ReservedBlockCount => _reservedBlockCount;
@@ -58,6 +88,9 @@ namespace Recyclable.Collections
public byte BlockSizePow2BitShift => _blockSizePow2BitShift; public byte BlockSizePow2BitShift => _blockSizePow2BitShift;
public int NextItemBlockIndex => _nextItemBlockIndex; public int NextItemBlockIndex => _nextItemBlockIndex;
public int NextItemIndex => _nextItemIndex; public int NextItemIndex => _nextItemIndex;
internal RecyclableCollectionVersion? _version;
public RecyclableCollectionVersion? Version => _version;
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
private static void CopyFollowingItems(RecyclableLongList<T> list, long destinationItemIndex) private static void CopyFollowingItems(RecyclableLongList<T> list, long destinationItemIndex)
@@ -631,16 +664,32 @@ namespace Recyclable.Collections
public T this[long index] public T this[long index]
{ {
get => _memoryBlocks[index >> _blockSizePow2BitShift][index & _blockSizeMinus1]; 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] public T this[int index]
{ {
get => _memoryBlocks[index >> _blockSizePow2BitShift][index & _blockSizeMinus1]; 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)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Add(T item) public void Add(T item)
@@ -667,6 +716,11 @@ namespace Recyclable.Collections
} }
_longCount++; _longCount++;
if (_version?.IsVersioned ?? false)
{
_version.Inc();
}
} }
public void AddRange(in T[] items) public void AddRange(in T[] items)
@@ -715,6 +769,11 @@ namespace Recyclable.Collections
_nextItemIndex = itemsSpan.Length; _nextItemIndex = itemsSpan.Length;
_longCount = targetCapacity; _longCount = targetCapacity;
_lastBlockWithData = targetBlockIndex; _lastBlockWithData = targetBlockIndex;
if (_version?.IsVersioned ?? false)
{
_version.Inc();
}
} }
public void AddRange(RecyclableList<T> items) public void AddRange(RecyclableList<T> items)
@@ -755,6 +814,11 @@ namespace Recyclable.Collections
_longCount = targetCapacity; _longCount = targetCapacity;
_nextItemBlockIndex = targetBlockIndex; _nextItemBlockIndex = targetBlockIndex;
_lastBlockWithData = targetBlockIndex - (itemsSpan.Length > 0 ? 0 : 1); _lastBlockWithData = targetBlockIndex - (itemsSpan.Length > 0 ? 0 : 1);
if (_version?.IsVersioned ?? false)
{
_version.Inc();
}
} }
public void AddRange(ReadOnlySpan<T> itemsSpan) public void AddRange(ReadOnlySpan<T> itemsSpan)
@@ -802,6 +866,11 @@ namespace Recyclable.Collections
_nextItemIndex = itemsSpan.Length; _nextItemIndex = itemsSpan.Length;
_longCount = targetCapacity; _longCount = targetCapacity;
_lastBlockWithData = targetBlockIndex; _lastBlockWithData = targetBlockIndex;
if (_version?.IsVersioned ?? false)
{
_version.Inc();
}
} }
public void AddRange(RecyclableLongList<T> items) public void AddRange(RecyclableLongList<T> items)
@@ -874,6 +943,11 @@ namespace Recyclable.Collections
} }
_longCount = targetCapacity; _longCount = targetCapacity;
if (_version?.IsVersioned ?? false)
{
_version.Inc();
}
} }
public void AddRange(List<T> items) public void AddRange(List<T> items)
@@ -921,6 +995,11 @@ namespace Recyclable.Collections
_nextItemBlockIndex = targetBlockIndex; _nextItemBlockIndex = targetBlockIndex;
_longCount = targetCapacity; _longCount = targetCapacity;
_lastBlockWithData = targetBlockIndex - (_nextItemIndex > 0 ? 0 : 1); _lastBlockWithData = targetBlockIndex - (_nextItemIndex > 0 ? 0 : 1);
if (_version?.IsVersioned ?? false)
{
_version.Inc();
}
} }
public void AddRange(IList<T> items) public void AddRange(IList<T> items)
@@ -975,6 +1054,11 @@ namespace Recyclable.Collections
{ {
_defaultBlockArrayPool.Return(itemsBuffer, NeedsClearing); _defaultBlockArrayPool.Return(itemsBuffer, NeedsClearing);
} }
if (_version?.IsVersioned ?? false)
{
_version.Inc();
}
} }
} }
@@ -983,40 +1067,41 @@ namespace Recyclable.Collections
if (source is RecyclableLongList<T> sourceRecyclableLongList) if (source is RecyclableLongList<T> sourceRecyclableLongList)
{ {
AddRange(sourceRecyclableLongList); AddRange(sourceRecyclableLongList);
return;
} }
else if (source is RecyclableList<T> sourceRecyclableList)
if (source is RecyclableList<T> sourceRecyclableList)
{ {
AddRange(sourceRecyclableList); AddRange(sourceRecyclableList);
return;
} }
else if (source is T[] sourceArray)
if (source is T[] sourceArray)
{ {
AddRange(sourceArray); AddRange(sourceArray);
return;
} }
else if (source is List<T> sourceList)
if (source is List<T> sourceList)
{ {
AddRange(sourceList); AddRange(sourceList);
return;
} }
else if (source is IList<T> sourceIList)
if (source is IList<T> sourceIList)
{ {
AddRange(sourceIList); AddRange(sourceIList);
return;
} }
else if (source.TryGetNonEnumeratedCount(out var requiredAdditionalCapacity))
if (source.TryGetNonEnumeratedCount(out var requiredAdditionalCapacity))
{ {
AddRangeWithKnownCount(source, 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() public void Clear()
@@ -1045,6 +1130,11 @@ namespace Recyclable.Collections
_nextItemIndex = 0; _nextItemIndex = 0;
_lastBlockWithData = RecyclableDefaults.ItemNotFoundIndex; _lastBlockWithData = RecyclableDefaults.ItemNotFoundIndex;
_longCount = 0; _longCount = 0;
if (_version?.IsVersioned ?? false)
{
_version.Inc();
}
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [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 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)] [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public int IndexOf(T item) public int IndexOf(T item)
@@ -1188,6 +1288,11 @@ namespace Recyclable.Collections
#pragma warning restore CS8601 #pragma warning restore CS8601
} }
if (_version?.IsVersioned ?? false)
{
_version.Inc();
}
return true; return true;
} }
@@ -1217,6 +1322,11 @@ namespace Recyclable.Collections
} }
_lastBlockWithData--; _lastBlockWithData--;
if (_version?.IsVersioned ?? false)
{
_version.Inc();
}
} }
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
@@ -1256,6 +1366,11 @@ namespace Recyclable.Collections
_memoryBlocks[_nextItemBlockIndex][_nextItemIndex] = default; _memoryBlocks[_nextItemBlockIndex][_nextItemIndex] = default;
#pragma warning restore CS8601 #pragma warning restore CS8601
} }
if (_version?.IsVersioned ?? false)
{
_version.Inc();
}
} }
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
@@ -1294,9 +1409,12 @@ namespace Recyclable.Collections
_memoryBlocks[_nextItemBlockIndex][_nextItemIndex] = default; _memoryBlocks[_nextItemBlockIndex][_nextItemIndex] = default;
#pragma warning restore CS8601 #pragma warning restore CS8601
} }
}
IEnumerator IEnumerable.GetEnumerator() => RecyclableLongList<T>.RecyclableLongListHelpers.Enumerate(_memoryBlocks, _blockSize, LongCount).GetEnumerator(); if (_version?.IsVersioned ?? false)
{
_version.Inc();
}
}
~RecyclableLongList() ~RecyclableLongList()
{ {
@@ -1315,6 +1433,13 @@ namespace Recyclable.Collections
_memoryBlocksPool.Return(_memoryBlocks, NeedsClearing); _memoryBlocksPool.Return(_memoryBlocks, NeedsClearing);
} }
if (_version != null)
{
//var toReturn = _version;
_version = null;
//RecyclableCollectionVersionPool.Shared.Return(toReturn);
}
GC.SuppressFinalize(this); 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);
}
}