RecyclableArrayList performance improvements.
This commit is contained in:
1 parent
012fc3b21f
commit
4aa32a32f7
7 files changed
+134
-47
No files matched your search
@@ -4,9 +4,10 @@ using Recyclable.Collections.Benchmarks;
|
|||||||
//BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(new[] { "-f*" });
|
//BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(new[] { "-f*" });
|
||||||
|
|
||||||
BenchmarkRunner.Run<RecyclableCollectionsBenchmarks>();
|
BenchmarkRunner.Run<RecyclableCollectionsBenchmarks>();
|
||||||
|
//BenchmarkRunner.Run<RefVsInstanceMemberBenchmarks>();
|
||||||
|
|
||||||
//var benchmark = new RecyclableCollectionsBenchmarks();
|
//var benchmark = new RecyclableCollectionsBenchmarks();
|
||||||
//benchmark.RecyclableArrayList_Add();
|
//benchmark.RecyclableArrayList_AddRange_WithCapacity();
|
||||||
//benchmark.RecyclableArrayList_Add_WithCapacity();
|
//benchmark.RecyclableArrayList_Add_WithCapacity();
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ namespace Recyclable.Collections.Benchmarks
|
|||||||
DoNothing(list);
|
DoNothing(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Benchmark]
|
//[Benchmark]
|
||||||
public void List_Add()
|
public void List_Add()
|
||||||
{
|
{
|
||||||
var data = TestObjects;
|
var data = TestObjects;
|
||||||
@@ -39,7 +39,7 @@ namespace Recyclable.Collections.Benchmarks
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Benchmark(Baseline = true)]
|
//[Benchmark(Baseline = true)]
|
||||||
public void List_Add_WithCapacity()
|
public void List_Add_WithCapacity()
|
||||||
{
|
{
|
||||||
var data = TestObjects;
|
var data = TestObjects;
|
||||||
@@ -59,7 +59,7 @@ namespace Recyclable.Collections.Benchmarks
|
|||||||
list.AddRange(data);
|
list.AddRange(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Benchmark]
|
[Benchmark(Baseline = true)]
|
||||||
public void List_AddRange_WithCapacity()
|
public void List_AddRange_WithCapacity()
|
||||||
{
|
{
|
||||||
var data = TestObjects;
|
var data = TestObjects;
|
||||||
|
|||||||
+2
-2
@@ -41,7 +41,7 @@ namespace Recyclable.Collections.Benchmarks
|
|||||||
DoNothing(list);
|
DoNothing(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Benchmark]
|
//[Benchmark]
|
||||||
public void RecyclableArrayList_Add()
|
public void RecyclableArrayList_Add()
|
||||||
{
|
{
|
||||||
var data = TestObjects;
|
var data = TestObjects;
|
||||||
@@ -53,7 +53,7 @@ namespace Recyclable.Collections.Benchmarks
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Benchmark]
|
//[Benchmark]
|
||||||
public void RecyclableArrayList_Add_WithCapacity()
|
public void RecyclableArrayList_Add_WithCapacity()
|
||||||
{
|
{
|
||||||
var data = TestObjects;
|
var data = TestObjects;
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
using BenchmarkDotNet.Attributes;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection.Metadata.Ecma335;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Recyclable.Collections.Benchmarks
|
||||||
|
{
|
||||||
|
public class RefVsInstanceMemberBenchmarks
|
||||||
|
{
|
||||||
|
private readonly TestCase _testCase = new();
|
||||||
|
private void DoNothing(int count)
|
||||||
|
{
|
||||||
|
_ = count;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestCase
|
||||||
|
{
|
||||||
|
private int _count = 123;
|
||||||
|
public int Count
|
||||||
|
{
|
||||||
|
get => _count;
|
||||||
|
set => _count = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ref int GetRefCount()
|
||||||
|
{
|
||||||
|
ref int count = ref _count;
|
||||||
|
count = 234;
|
||||||
|
_ = Add(count);
|
||||||
|
return ref count;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int Add(int count)
|
||||||
|
{
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetCount()
|
||||||
|
{
|
||||||
|
_count = 234;
|
||||||
|
_ = Add(_count);
|
||||||
|
return _count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public void Ref()
|
||||||
|
{
|
||||||
|
var count = _testCase.GetRefCount();
|
||||||
|
DoNothing(count);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark(Baseline = true)]
|
||||||
|
public void InstanceMember()
|
||||||
|
{
|
||||||
|
var count = _testCase.GetCount();
|
||||||
|
DoNothing(count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,19 +1,27 @@
|
|||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace Recyclable.Collections
|
namespace Recyclable.Collections
|
||||||
{
|
{
|
||||||
public class RecyclableArrayList<T> : IEnumerable<T>, IDisposable
|
public class RecyclableArrayList<T> : IEnumerable<T>, IDisposable
|
||||||
{
|
{
|
||||||
|
private static readonly ArrayPool<T> _arrayPool = ArrayPool<T>.Create();
|
||||||
private static readonly IEqualityComparer<T> _equalityComparer = EqualityComparer<T>.Default;
|
private static readonly IEqualityComparer<T> _equalityComparer = EqualityComparer<T>.Default;
|
||||||
|
|
||||||
protected T[] _memory;
|
protected T[] _memory;
|
||||||
|
|
||||||
|
private static void ThrowArgumentOutOfRangeException()
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException("index");
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
protected static T[] SetNewLength(in T[]? source, in int newSize)
|
protected static T[] SetNewLength(in T[]? source, in int newSize)
|
||||||
{
|
{
|
||||||
var sourceLength = source?.Length ?? 0;
|
var sourceLength = source?.Length ?? 0;
|
||||||
T[] newArray;
|
T[] newArray;
|
||||||
newArray = newSize.RentArrayFromPool<T>();
|
newArray = newSize.RentArrayFromPool(_arrayPool);
|
||||||
switch (newSize >= sourceLength)
|
switch (newSize >= sourceLength)
|
||||||
{
|
{
|
||||||
case true:
|
case true:
|
||||||
@@ -22,28 +30,31 @@ namespace Recyclable.Collections
|
|||||||
Memory<T> sourceMemory = new(source);
|
Memory<T> sourceMemory = new(source);
|
||||||
Memory<T> newArrayMemory = new(newArray);
|
Memory<T> newArrayMemory = new(newArray);
|
||||||
sourceMemory.CopyTo(newArrayMemory);
|
sourceMemory.CopyTo(newArrayMemory);
|
||||||
source!.ReturnToPool();
|
source!.ReturnToPool(_arrayPool);
|
||||||
}
|
}
|
||||||
|
|
||||||
return newArray;
|
return newArray;
|
||||||
|
|
||||||
case false:
|
case false:
|
||||||
Span<T> sourceSpan = source.AsSpan();
|
Span<T> sourceSpan = new(source);
|
||||||
Span<T> newArraySpan = newArray.AsSpan();
|
Span<T> newArraySpan = new(newArray);
|
||||||
sourceSpan = sourceSpan[..newSize];
|
sourceSpan = sourceSpan[..newSize];
|
||||||
sourceSpan.CopyTo(newArraySpan);
|
sourceSpan.CopyTo(newArraySpan);
|
||||||
source!.ReturnToPool();
|
source!.ReturnToPool(_arrayPool);
|
||||||
return newArray;
|
return newArray;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected ref int EnsureCapacity(in int requestedCapacity)
|
protected int EnsureCapacity(in int requestedCapacity)
|
||||||
{
|
{
|
||||||
|
int oldCapacity = _capacity;
|
||||||
|
ref T[] memory = ref _memory;
|
||||||
|
|
||||||
int newCapacity;
|
int newCapacity;
|
||||||
switch (_capacity > 0)
|
switch (oldCapacity > 0)
|
||||||
{
|
{
|
||||||
case true:
|
case true:
|
||||||
newCapacity = _capacity;
|
newCapacity = oldCapacity;
|
||||||
while (newCapacity < requestedCapacity)
|
while (newCapacity < requestedCapacity)
|
||||||
{
|
{
|
||||||
newCapacity *= 2;
|
newCapacity *= 2;
|
||||||
@@ -56,10 +67,10 @@ namespace Recyclable.Collections
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
_memory = SetNewLength(_memory, newCapacity);
|
memory = SetNewLength(memory, newCapacity);
|
||||||
newCapacity = _memory.Length;
|
newCapacity = memory.Length;
|
||||||
_capacity = newCapacity;
|
_capacity = newCapacity;
|
||||||
return ref _capacity;
|
return newCapacity;
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma warning disable CS8618 // _memory will be initialized when the 1st item is added
|
#pragma warning disable CS8618 // _memory will be initialized when the 1st item is added
|
||||||
@@ -76,19 +87,15 @@ namespace Recyclable.Collections
|
|||||||
if (initialCapacity > 0)
|
if (initialCapacity > 0)
|
||||||
{
|
{
|
||||||
_memory = SetNewLength(_memory, initialCapacity);
|
_memory = SetNewLength(_memory, initialCapacity);
|
||||||
_capacity = initialCapacity;
|
_capacity = _memory.Length;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public RecyclableArrayList(IEnumerable<T> source, int initialCapacity = RecyclableDefaults.Capacity)
|
public RecyclableArrayList(IEnumerable<T> source, int initialCapacity = RecyclableDefaults.Capacity)
|
||||||
{
|
{
|
||||||
_memory = SetNewLength(_memory, initialCapacity);
|
_memory = SetNewLength(_memory, initialCapacity);
|
||||||
_capacity = initialCapacity;
|
_capacity = _memory.Length;
|
||||||
|
AddRange(source);
|
||||||
foreach (var item in source)
|
|
||||||
{
|
|
||||||
Add(item);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public T this[int index]
|
public T this[int index]
|
||||||
@@ -106,7 +113,7 @@ namespace Recyclable.Collections
|
|||||||
if (_capacity != value)
|
if (_capacity != value)
|
||||||
{
|
{
|
||||||
_memory = SetNewLength(_memory, value);
|
_memory = SetNewLength(_memory, value);
|
||||||
_capacity = value;
|
_capacity = _memory.Length;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -122,8 +129,7 @@ namespace Recyclable.Collections
|
|||||||
_ = EnsureCapacity(requestedCapacity);
|
_ = EnsureCapacity(requestedCapacity);
|
||||||
}
|
}
|
||||||
|
|
||||||
_memory[Count] = item;
|
_memory[Count++] = item;
|
||||||
Count++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddRange(in T[] items)
|
public void AddRange(in T[] items)
|
||||||
@@ -135,10 +141,11 @@ namespace Recyclable.Collections
|
|||||||
_ = EnsureCapacity(targetCapacity);
|
_ = EnsureCapacity(targetCapacity);
|
||||||
}
|
}
|
||||||
|
|
||||||
Span<T> itemsSpan = items.AsSpan();
|
Span<T> itemsSpan = new(items);
|
||||||
|
Span<T> targetSpan = new(_memory);
|
||||||
for (int sourceItemIdx = 0, targetItemIdx = Count; sourceItemIdx < sourceItemsCount;)
|
for (int sourceItemIdx = 0, targetItemIdx = Count; sourceItemIdx < sourceItemsCount;)
|
||||||
{
|
{
|
||||||
_memory[targetItemIdx++] = itemsSpan[sourceItemIdx++];
|
targetSpan[targetItemIdx++] = itemsSpan[sourceItemIdx++];
|
||||||
}
|
}
|
||||||
|
|
||||||
Count = targetCapacity;
|
Count = targetCapacity;
|
||||||
@@ -153,14 +160,23 @@ namespace Recyclable.Collections
|
|||||||
_ = EnsureCapacity(targetCapacity);
|
_ = EnsureCapacity(targetCapacity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Span<T> targetSpan = new(_memory);
|
||||||
for (int sourceItemIdx = 0, targetItemIdx = Count; sourceItemIdx < sourceItemsCount;)
|
for (int sourceItemIdx = 0, targetItemIdx = Count; sourceItemIdx < sourceItemsCount;)
|
||||||
{
|
{
|
||||||
_memory[targetItemIdx++] = items[sourceItemIdx++];
|
targetSpan[targetItemIdx++] = items[sourceItemIdx++];
|
||||||
}
|
}
|
||||||
|
|
||||||
Count = targetCapacity;
|
Count = targetCapacity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void AddRange(IEnumerable<T> source)
|
||||||
|
{
|
||||||
|
foreach (var item in source)
|
||||||
|
{
|
||||||
|
Add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void Clear()
|
public void Clear()
|
||||||
{
|
{
|
||||||
Count = 0;
|
Count = 0;
|
||||||
@@ -172,20 +188,22 @@ namespace Recyclable.Collections
|
|||||||
|
|
||||||
protected static IEnumerable<T> Enumerate(RecyclableArrayList<T> list)
|
protected static IEnumerable<T> Enumerate(RecyclableArrayList<T> list)
|
||||||
{
|
{
|
||||||
for (var i = 0; i < list.Count; i++)
|
int count = list.Count;
|
||||||
|
var memory = list._memory;
|
||||||
|
for (var i = 0; i < count; i++)
|
||||||
{
|
{
|
||||||
yield return list._memory[i];
|
yield return memory[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public int IndexOf(T itemToFind)
|
public int IndexOf(T itemToFind)
|
||||||
{
|
{
|
||||||
var memory = _memory;
|
Span<T> memorySpan = _memory;
|
||||||
int itemCount = Count;
|
int itemCount = Count;
|
||||||
var equalityComparer = _equalityComparer;
|
var equalityComparer = _equalityComparer;
|
||||||
for (var itemIdx = 0; itemIdx < itemCount; itemIdx++)
|
for (var itemIdx = 0; itemIdx < itemCount; itemIdx++)
|
||||||
{
|
{
|
||||||
var item = memory[itemIdx];
|
var item = memorySpan[itemIdx];
|
||||||
if (equalityComparer.Equals(item, itemToFind))
|
if (equalityComparer.Equals(item, itemToFind))
|
||||||
{
|
{
|
||||||
return itemIdx;
|
return itemIdx;
|
||||||
@@ -203,12 +221,13 @@ namespace Recyclable.Collections
|
|||||||
_ = EnsureCapacity(requestedCapacity);
|
_ = EnsureCapacity(requestedCapacity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Span<T> memorySpan = new(_memory);
|
||||||
for (var toMoveIdx = Count - 1; toMoveIdx >= index; toMoveIdx--)
|
for (var toMoveIdx = Count - 1; toMoveIdx >= index; toMoveIdx--)
|
||||||
{
|
{
|
||||||
_memory[toMoveIdx + 1] = _memory[toMoveIdx];
|
memorySpan[toMoveIdx + 1] = memorySpan[toMoveIdx];
|
||||||
}
|
}
|
||||||
|
|
||||||
_memory[index] = item;
|
memorySpan[index] = item;
|
||||||
Count++;
|
Count++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,7 +254,8 @@ namespace Recyclable.Collections
|
|||||||
|
|
||||||
if (index >= oldCount)
|
if (index >= oldCount)
|
||||||
{
|
{
|
||||||
throw new ArgumentOutOfRangeException(nameof(index));
|
ThrowArgumentOutOfRangeException();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_memory.CopyItems(index, oldCountMinus1, ref _memory);
|
_memory.CopyItems(index, oldCountMinus1, ref _memory);
|
||||||
@@ -251,7 +271,7 @@ namespace Recyclable.Collections
|
|||||||
{
|
{
|
||||||
Clear();
|
Clear();
|
||||||
Capacity = 0;
|
Capacity = 0;
|
||||||
_memory.ReturnToPool();
|
_memory.ReturnToPool(_arrayPool);
|
||||||
GC.SuppressFinalize(this);
|
GC.SuppressFinalize(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -128,21 +128,21 @@ namespace Recyclable.Collections
|
|||||||
//public static RecyclableList<T> ToRecyclableList<T>(this IEnumerable<T> values, int blockSize = RecyclableDefaults.BlockSize) => new(values, blockSize);
|
//public static RecyclableList<T> ToRecyclableList<T>(this IEnumerable<T> values, int blockSize = RecyclableDefaults.BlockSize) => new(values, blockSize);
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static T[] RentArrayFromPool<T>(this int minSize) => (minSize >= _minPooledArraySize)
|
public static T[] RentArrayFromPool<T>(this int minSize, ArrayPool<T> arrayPool) => (minSize >= _minPooledArraySize)
|
||||||
? ArrayPool<T>.Shared.Rent(minSize)
|
? arrayPool.Rent(minSize)
|
||||||
: new T[minSize];
|
: new T[minSize];
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static T[] RentArrayFromPool<T>(this long minSize) => (minSize is >= _minPooledArraySize and <= int.MaxValue)
|
public static T[] RentArrayFromPool<T>(this long minSize, ArrayPool<T> arrayPool) => (minSize is >= _minPooledArraySize and <= int.MaxValue)
|
||||||
? ArrayPool<T>.Shared.Rent((int)minSize)
|
? arrayPool.Rent((int)minSize)
|
||||||
: new T[minSize];
|
: new T[minSize];
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static void ReturnToPool<T>(this T[] array)
|
public static void ReturnToPool<T>(this T[] array, ArrayPool<T> arrayPool)
|
||||||
{
|
{
|
||||||
if (array.LongLength is > _minPooledArraySize and <= int.MaxValue)
|
if (array.LongLength is > _minPooledArraySize and <= int.MaxValue)
|
||||||
{
|
{
|
||||||
ArrayPool<T>.Shared.Return(array);
|
arrayPool.Return(array);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,6 +154,7 @@ namespace Recyclable.Collections
|
|||||||
// }
|
// }
|
||||||
//}
|
//}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static long LimitTo(this long value, int limit)
|
public static long LimitTo(this long value, int limit)
|
||||||
=> (value <= limit) ? value : limit;
|
=> (value <= limit) ? value : limit;
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
using System.Collections;
|
using System.Buffers;
|
||||||
|
using System.Collections;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace Recyclable.Collections
|
namespace Recyclable.Collections
|
||||||
{
|
{
|
||||||
public class RecyclableQueue<T> : IRecyclableOwner<T>, IList<T>, IDisposable
|
public class RecyclableQueue<T> : IRecyclableOwner<T>, IList<T>, IDisposable
|
||||||
{
|
{
|
||||||
|
private static readonly ArrayPool<T> _arrayPool = ArrayPool<T>.Create();
|
||||||
private static readonly IEqualityComparer<T> _equalityComparer = EqualityComparer<T>.Default;
|
private static readonly IEqualityComparer<T> _equalityComparer = EqualityComparer<T>.Default;
|
||||||
|
|
||||||
private bool _disposedValue;
|
private bool _disposedValue;
|
||||||
@@ -129,7 +131,7 @@ namespace Recyclable.Collections
|
|||||||
|
|
||||||
public void AddBlock()
|
public void AddBlock()
|
||||||
{
|
{
|
||||||
T[] newArray = BlockSize.RentArrayFromPool<T>();
|
T[] newArray = BlockSize.RentArrayFromPool<T>(_arrayPool);
|
||||||
Memory.Add(newArray);
|
Memory.Add(newArray);
|
||||||
Capacity += BlockSize;
|
Capacity += BlockSize;
|
||||||
}
|
}
|
||||||
@@ -179,7 +181,7 @@ namespace Recyclable.Collections
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Memory[index].ReturnToPool();
|
Memory[index].ReturnToPool(_arrayPool);
|
||||||
Memory.RemoveAt(index);
|
Memory.RemoveAt(index);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
|
|||||||
Reference in new issue
Block a user