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()
{
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<long>)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<long>)TestObjectsAsRecyclableLongList)
{
DoNothing(item);
}
}
}
}
@@ -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<RecyclableCollectionsBenchmarkSource>
@@ -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; }
+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 const int BlockIndexLimit = int.MaxValue;
public const int BlockSize = 16_384;
public const int Capacity = 1;
public const long MinItemsCountForParallelization = 850_000;
@@ -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<T>
{
[StructLayout(LayoutKind.Auto)]
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct Enumerator : IEnumerator<T>
{
private int _currentIndex;
private readonly ulong _enumeratorVersion;
private readonly RecyclableList<T> _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<T> _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<T> 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
}
@@ -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);
// 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<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);
}
}
}