From 4aa32a32f76b1c17fad60ff21963ed09e9bf75f4 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sat, 14 Jan 2023 17:13:37 +0100 Subject: [PATCH] RecyclableArrayList performance improvements. --- Recyclable.Collections.Benchmarks/Program.cs | 3 +- .../RecyclableCollectionsBenchmarks.List.cs | 6 +- ...llectionsBenchmarks.RecyclableArrayList.cs | 4 +- .../RefVsInstanceMemberBenchmarks.cs | 63 ++++++++++++++ Recyclable.Collections/RecyclableArrayList.cs | 84 ++++++++++++------- .../RecyclableListExtensions.cs | 13 +-- Recyclable.Collections/RecyclableQueue.cs | 8 +- 7 files changed, 134 insertions(+), 47 deletions(-) create mode 100644 Recyclable.Collections.Benchmarks/RefVsInstanceMemberBenchmarks.cs diff --git a/Recyclable.Collections.Benchmarks/Program.cs b/Recyclable.Collections.Benchmarks/Program.cs index fdc21ac..52450ac 100644 --- a/Recyclable.Collections.Benchmarks/Program.cs +++ b/Recyclable.Collections.Benchmarks/Program.cs @@ -4,9 +4,10 @@ using Recyclable.Collections.Benchmarks; //BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(new[] { "-f*" }); BenchmarkRunner.Run(); +//BenchmarkRunner.Run(); //var benchmark = new RecyclableCollectionsBenchmarks(); -//benchmark.RecyclableArrayList_Add(); +//benchmark.RecyclableArrayList_AddRange_WithCapacity(); //benchmark.RecyclableArrayList_Add_WithCapacity(); diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs index e7f3324..af73248 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs @@ -27,7 +27,7 @@ namespace Recyclable.Collections.Benchmarks DoNothing(list); } - [Benchmark] + //[Benchmark] public void List_Add() { var data = TestObjects; @@ -39,7 +39,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark(Baseline = true)] + //[Benchmark(Baseline = true)] public void List_Add_WithCapacity() { var data = TestObjects; @@ -59,7 +59,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - [Benchmark] + [Benchmark(Baseline = true)] public void List_AddRange_WithCapacity() { var data = TestObjects; diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs index 11a8a33..5451f76 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs @@ -41,7 +41,7 @@ namespace Recyclable.Collections.Benchmarks DoNothing(list); } - [Benchmark] + //[Benchmark] public void RecyclableArrayList_Add() { var data = TestObjects; @@ -53,7 +53,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark] + //[Benchmark] public void RecyclableArrayList_Add_WithCapacity() { var data = TestObjects; diff --git a/Recyclable.Collections.Benchmarks/RefVsInstanceMemberBenchmarks.cs b/Recyclable.Collections.Benchmarks/RefVsInstanceMemberBenchmarks.cs new file mode 100644 index 0000000..ed3c1b3 --- /dev/null +++ b/Recyclable.Collections.Benchmarks/RefVsInstanceMemberBenchmarks.cs @@ -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); + } + } +} diff --git a/Recyclable.Collections/RecyclableArrayList.cs b/Recyclable.Collections/RecyclableArrayList.cs index 81ab4dc..a6711b0 100644 --- a/Recyclable.Collections/RecyclableArrayList.cs +++ b/Recyclable.Collections/RecyclableArrayList.cs @@ -1,19 +1,27 @@ using System.Buffers; using System.Collections; +using System.Runtime.CompilerServices; namespace Recyclable.Collections { public class RecyclableArrayList : IEnumerable, IDisposable { + private static readonly ArrayPool _arrayPool = ArrayPool.Create(); private static readonly IEqualityComparer _equalityComparer = EqualityComparer.Default; protected T[] _memory; + private static void ThrowArgumentOutOfRangeException() + { + throw new ArgumentOutOfRangeException("index"); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] protected static T[] SetNewLength(in T[]? source, in int newSize) { var sourceLength = source?.Length ?? 0; T[] newArray; - newArray = newSize.RentArrayFromPool(); + newArray = newSize.RentArrayFromPool(_arrayPool); switch (newSize >= sourceLength) { case true: @@ -22,28 +30,31 @@ namespace Recyclable.Collections Memory sourceMemory = new(source); Memory newArrayMemory = new(newArray); sourceMemory.CopyTo(newArrayMemory); - source!.ReturnToPool(); + source!.ReturnToPool(_arrayPool); } return newArray; case false: - Span sourceSpan = source.AsSpan(); - Span newArraySpan = newArray.AsSpan(); + Span sourceSpan = new(source); + Span newArraySpan = new(newArray); sourceSpan = sourceSpan[..newSize]; sourceSpan.CopyTo(newArraySpan); - source!.ReturnToPool(); + source!.ReturnToPool(_arrayPool); 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; - switch (_capacity > 0) + switch (oldCapacity > 0) { case true: - newCapacity = _capacity; + newCapacity = oldCapacity; while (newCapacity < requestedCapacity) { newCapacity *= 2; @@ -56,10 +67,10 @@ namespace Recyclable.Collections break; } - _memory = SetNewLength(_memory, newCapacity); - newCapacity = _memory.Length; + memory = SetNewLength(memory, newCapacity); + newCapacity = memory.Length; _capacity = newCapacity; - return ref _capacity; + return newCapacity; } #pragma warning disable CS8618 // _memory will be initialized when the 1st item is added @@ -76,19 +87,15 @@ namespace Recyclable.Collections if (initialCapacity > 0) { _memory = SetNewLength(_memory, initialCapacity); - _capacity = initialCapacity; + _capacity = _memory.Length; } } public RecyclableArrayList(IEnumerable source, int initialCapacity = RecyclableDefaults.Capacity) { _memory = SetNewLength(_memory, initialCapacity); - _capacity = initialCapacity; - - foreach (var item in source) - { - Add(item); - } + _capacity = _memory.Length; + AddRange(source); } public T this[int index] @@ -106,7 +113,7 @@ namespace Recyclable.Collections if (_capacity != value) { _memory = SetNewLength(_memory, value); - _capacity = value; + _capacity = _memory.Length; } } } @@ -122,8 +129,7 @@ namespace Recyclable.Collections _ = EnsureCapacity(requestedCapacity); } - _memory[Count] = item; - Count++; + _memory[Count++] = item; } public void AddRange(in T[] items) @@ -135,10 +141,11 @@ namespace Recyclable.Collections _ = EnsureCapacity(targetCapacity); } - Span itemsSpan = items.AsSpan(); + Span itemsSpan = new(items); + Span targetSpan = new(_memory); for (int sourceItemIdx = 0, targetItemIdx = Count; sourceItemIdx < sourceItemsCount;) { - _memory[targetItemIdx++] = itemsSpan[sourceItemIdx++]; + targetSpan[targetItemIdx++] = itemsSpan[sourceItemIdx++]; } Count = targetCapacity; @@ -153,14 +160,23 @@ namespace Recyclable.Collections _ = EnsureCapacity(targetCapacity); } + Span targetSpan = new(_memory); for (int sourceItemIdx = 0, targetItemIdx = Count; sourceItemIdx < sourceItemsCount;) { - _memory[targetItemIdx++] = items[sourceItemIdx++]; + targetSpan[targetItemIdx++] = items[sourceItemIdx++]; } Count = targetCapacity; } + public void AddRange(IEnumerable source) + { + foreach (var item in source) + { + Add(item); + } + } + public void Clear() { Count = 0; @@ -172,20 +188,22 @@ namespace Recyclable.Collections protected static IEnumerable Enumerate(RecyclableArrayList 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) { - var memory = _memory; + Span memorySpan = _memory; int itemCount = Count; var equalityComparer = _equalityComparer; for (var itemIdx = 0; itemIdx < itemCount; itemIdx++) { - var item = memory[itemIdx]; + var item = memorySpan[itemIdx]; if (equalityComparer.Equals(item, itemToFind)) { return itemIdx; @@ -203,12 +221,13 @@ namespace Recyclable.Collections _ = EnsureCapacity(requestedCapacity); } + Span memorySpan = new(_memory); 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++; } @@ -235,7 +254,8 @@ namespace Recyclable.Collections if (index >= oldCount) { - throw new ArgumentOutOfRangeException(nameof(index)); + ThrowArgumentOutOfRangeException(); + return; } _memory.CopyItems(index, oldCountMinus1, ref _memory); @@ -251,7 +271,7 @@ namespace Recyclable.Collections { Clear(); Capacity = 0; - _memory.ReturnToPool(); + _memory.ReturnToPool(_arrayPool); GC.SuppressFinalize(this); } } diff --git a/Recyclable.Collections/RecyclableListExtensions.cs b/Recyclable.Collections/RecyclableListExtensions.cs index a027410..03ae6dc 100644 --- a/Recyclable.Collections/RecyclableListExtensions.cs +++ b/Recyclable.Collections/RecyclableListExtensions.cs @@ -128,21 +128,21 @@ namespace Recyclable.Collections //public static RecyclableList ToRecyclableList(this IEnumerable values, int blockSize = RecyclableDefaults.BlockSize) => new(values, blockSize); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T[] RentArrayFromPool(this int minSize) => (minSize >= _minPooledArraySize) - ? ArrayPool.Shared.Rent(minSize) + public static T[] RentArrayFromPool(this int minSize, ArrayPool arrayPool) => (minSize >= _minPooledArraySize) + ? arrayPool.Rent(minSize) : new T[minSize]; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T[] RentArrayFromPool(this long minSize) => (minSize is >= _minPooledArraySize and <= int.MaxValue) - ? ArrayPool.Shared.Rent((int)minSize) + public static T[] RentArrayFromPool(this long minSize, ArrayPool arrayPool) => (minSize is >= _minPooledArraySize and <= int.MaxValue) + ? arrayPool.Rent((int)minSize) : new T[minSize]; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ReturnToPool(this T[] array) + public static void ReturnToPool(this T[] array, ArrayPool arrayPool) { if (array.LongLength is > _minPooledArraySize and <= int.MaxValue) { - ArrayPool.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) => (value <= limit) ? value : limit; diff --git a/Recyclable.Collections/RecyclableQueue.cs b/Recyclable.Collections/RecyclableQueue.cs index 29c68cf..32ee242 100644 --- a/Recyclable.Collections/RecyclableQueue.cs +++ b/Recyclable.Collections/RecyclableQueue.cs @@ -1,10 +1,12 @@ -using System.Collections; +using System.Buffers; +using System.Collections; using System.Runtime.CompilerServices; namespace Recyclable.Collections { public class RecyclableQueue : IRecyclableOwner, IList, IDisposable { + private static readonly ArrayPool _arrayPool = ArrayPool.Create(); private static readonly IEqualityComparer _equalityComparer = EqualityComparer.Default; private bool _disposedValue; @@ -129,7 +131,7 @@ namespace Recyclable.Collections public void AddBlock() { - T[] newArray = BlockSize.RentArrayFromPool(); + T[] newArray = BlockSize.RentArrayFromPool(_arrayPool); Memory.Add(newArray); Capacity += BlockSize; } @@ -179,7 +181,7 @@ namespace Recyclable.Collections { try { - Memory[index].ReturnToPool(); + Memory[index].ReturnToPool(_arrayPool); Memory.RemoveAt(index); } finally