RecyclableArrayList performance improvements.

This commit is contained in:
mlemanczyk committed 2023-01-14 17:13:37 +01:00
1 parent 012fc3b21f
commit 4aa32a32f7
7 files changed
+134 -47

No files matched your search

+2 -1
View File
@@ -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;
@@ -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);
}
}
}
+52 -32
View File
@@ -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;
+5 -3
View File
@@ -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