Versioned enumerator for RecyclableList implemented. Benchmarks & tests added.
This commit is contained in:
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; }
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user