Versioned enumerator for RecyclableList implemented. Benchmarks & tests added.

This commit is contained in:
mlemanczyk committed 2023-06-25 00:05:17 +02:00
1 parent be746d65bf
commit d830a53006
7 files changed
+177 -205

No files matched your search

@@ -10,6 +10,14 @@
} }
} }
public void Array_VersionedForEach()
{
foreach (var item in TestObjects)
{
DoNothing(item);
}
}
public void List_ForEach() public void List_ForEach()
{ {
foreach (var item in TestObjectsAsList) foreach (var item in TestObjectsAsList)
@@ -18,6 +26,14 @@
} }
} }
public void List_VersionedForEach()
{
foreach (var item in TestObjectsAsList)
{
DoNothing(item);
}
}
public void PooledList_ForEach() public void PooledList_ForEach()
{ {
foreach (var item in TestObjectsAsPooledList) foreach (var item in TestObjectsAsPooledList)
@@ -26,6 +42,14 @@
} }
} }
public void PooledList_VersionedForEach()
{
foreach (var item in TestObjectsAsPooledList)
{
DoNothing(item);
}
}
public void RecyclableList_ForEach() public void RecyclableList_ForEach()
{ {
foreach (var item in TestObjectsAsRecyclableList) foreach (var item in TestObjectsAsRecyclableList)
@@ -34,6 +58,14 @@
} }
} }
public void RecyclableList_VersionedForEach()
{
foreach (var item in (IVersionedList<long>)TestObjectsAsRecyclableList)
{
DoNothing(item);
}
}
public void RecyclableLongList_ForEach() public void RecyclableLongList_ForEach()
{ {
foreach (var item in TestObjectsAsRecyclableLongList) foreach (var item in TestObjectsAsRecyclableLongList)
@@ -41,5 +73,13 @@
DoNothing(item); DoNothing(item);
} }
} }
public void RecyclableLongList_VersionedForEach()
{
foreach (var item in (IVersionedLongList<long>)TestObjectsAsRecyclableLongList)
{
DoNothing(item);
}
}
} }
} }
@@ -9,7 +9,7 @@ namespace Recyclable.Collections.Benchmarks
AddRangeWhenSourceIsIList, AddRangeWhenSourceIsList, AddRangeWhenSourceIsSameType, Contains_FirstItems, AddRangeWhenSourceIsIList, AddRangeWhenSourceIsList, AddRangeWhenSourceIsSameType, Contains_FirstItems,
Contains_LastItems, Count, Create_WithCapacity, Create, ForEach, 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, VersionedForEach
} }
public partial class RecyclableCollectionsBenchmarks : RecyclableBenchmarkBase<RecyclableCollectionsBenchmarkSource> public partial class RecyclableCollectionsBenchmarks : RecyclableBenchmarkBase<RecyclableCollectionsBenchmarkSource>
@@ -23,47 +23,48 @@ namespace Recyclable.Collections.Benchmarks
[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.ForEach RecyclableCollectionsBenchmarkType.ForEach,
//RecyclableCollectionsBenchmarkType.GetItem, RecyclableCollectionsBenchmarkType.GetItem,
//RecyclableCollectionsBenchmarkType.IndexOf_BestAndWorstCases, RecyclableCollectionsBenchmarkType.IndexOf_BestAndWorstCases,
//RecyclableCollectionsBenchmarkType.IndexOf_FirstItems, RecyclableCollectionsBenchmarkType.IndexOf_FirstItems,
//RecyclableCollectionsBenchmarkType.IndexOf_LastItems, RecyclableCollectionsBenchmarkType.IndexOf_LastItems,
//RecyclableCollectionsBenchmarkType.LongCount, RecyclableCollectionsBenchmarkType.LongCount,
//RecyclableCollectionsBenchmarkType.Remove_FirstItems, RecyclableCollectionsBenchmarkType.Remove_FirstItems,
//RecyclableCollectionsBenchmarkType.Remove_LastItems, RecyclableCollectionsBenchmarkType.Remove_LastItems,
//RecyclableCollectionsBenchmarkType.RemoveAt_FirstItems, RecyclableCollectionsBenchmarkType.RemoveAt_FirstItems,
//RecyclableCollectionsBenchmarkType.RemoveAt_LastItems, RecyclableCollectionsBenchmarkType.RemoveAt_LastItems,
//RecyclableCollectionsBenchmarkType.SetItem RecyclableCollectionsBenchmarkType.SetItem,
RecyclableCollectionsBenchmarkType.VersionedForEach
)] )]
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; }
+7
View File
@@ -0,0 +1,7 @@
namespace Recyclable.Collections
{
public interface IVersionedList<T> : IEnumerable<T>
{
new RecyclableList<T>.VersionedEnumerator GetEnumerator();
}
}
@@ -2,6 +2,7 @@
{ {
public static class RecyclableDefaults public static class RecyclableDefaults
{ {
public const int BlockIndexLimit = int.MaxValue;
public const int BlockSize = 16_384; public const int BlockSize = 16_384;
public const int Capacity = 1; public const int Capacity = 1;
public const long MinItemsCountForParallelization = 850_000; public const long MinItemsCountForParallelization = 850_000;
@@ -1,209 +1,50 @@
using System.Collections; using System.Collections;
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace Recyclable.Collections namespace Recyclable.Collections
{ {
public partial class RecyclableList<T> public partial class RecyclableList<T>
{ {
[StructLayout(LayoutKind.Auto)] [StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct Enumerator : IEnumerator<T> public struct Enumerator : IEnumerator<T>
{ {
private int _currentIndex; private static readonly bool _isReferenceType = !typeof(T).IsValueType;
private readonly ulong _enumeratorVersion;
private readonly RecyclableList<T> _list;
// & WAS SLOWER private int _currentItemIndex;
// private bool _isInValidState = false; private readonly RecyclableList<T> _list;
// private bool _endOfCollectionReached;
// private T _current;
#nullable disable #nullable disable
// [Pure] // [Pure]
public readonly T Current public readonly T Current => _list._memoryBlock[_currentItemIndex - 1];
{
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;
}
}
// [Pure] // [Pure]
object IEnumerator.Current readonly object IEnumerator.Current => _list._memoryBlock[_currentItemIndex - 1];
{ #nullable restore
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();
}
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() public bool MoveNext()
{ {
// & WAS SLOWER if (_currentItemIndex < _list._count)
// var list = _list;
if (_enumeratorVersion != _list._version)
{ {
throw new InvalidOperationException(); _currentItemIndex++;
// & 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++;
return true; 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; return false;
} }
public Enumerator(RecyclableList<T> list) public Enumerator(RecyclableList<T> list)
{ {
_currentIndex = 0; _currentItemIndex = 0;
_list = list; _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() 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() void IEnumerator.Reset()
{ {
_currentIndex = 0; _currentItemIndex = 0;
// & WAS SLOWER
// _endOfCollectionReached = false;
// _isInValidState = false;
// _current = default;
} }
} }
} }
#nullable restore
} }
@@ -0,0 +1,65 @@
using System.Collections;
using System.Diagnostics.Contracts;
using System.Runtime.InteropServices;
namespace Recyclable.Collections
{
public partial class RecyclableList<T> : IVersionedList<T>
{
VersionedEnumerator IVersionedList<T>.GetEnumerator() => new(this);
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct VersionedEnumerator : IEnumerator<T>
{
private int _currentItemIndex;
private readonly ulong _enumeratorVersion;
private readonly RecyclableList<T> _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<T> list)
{
_currentItemIndex = 0;
_list = list;
_enumeratorVersion = list._version;
}
public readonly void Dispose()
{
}
void IEnumerator.Reset()
{
if (_enumeratorVersion != _list._version)
{
throw new InvalidOperationException();
}
_currentItemIndex = 0;
}
}
}
}
@@ -336,7 +336,6 @@ namespace Recyclable.CollectionsTests
list.CopyTo(copiedItems, 0); list.CopyTo(copiedItems, 0);
// Validate // Validate
_ = copiedItems.Length.Should().Be((int)itemsCount);
_ = copiedItems.Should().ContainInConsecutiveOrder(testData); _ = copiedItems.Should().ContainInConsecutiveOrder(testData);
} }
@@ -551,5 +550,23 @@ namespace Recyclable.CollectionsTests
_ = list.Should().ContainInConsecutiveOrder(testData.Take((int)(itemsCount - deleted))); _ = list.Should().ContainInConsecutiveOrder(testData.Take((int)(itemsCount - deleted)));
} }
} }
[Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))]
public void VersionedEnumerateShouldYieldAllItemsInCorrectOrder(string testCase, IEnumerable<long> testData, long itemsCount)
{
// Prepare
using var list = new RecyclableList<long>(testData);
// Act
var yieldedItems = new List<long>((int)itemsCount);
foreach (var item in (IVersionedList<long>)list)
{
yieldedItems.Add(item);
}
// Validate
_ = yieldedItems.Should().ContainInConsecutiveOrder(testData);
}
} }
} }