From 880abd605eaab61784ec12ac5d7117bf72d2483a Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Wed, 11 Jan 2023 21:21:42 +0100 Subject: [PATCH] RecyclableBlockList renamed to RecyclableArrayList for clarity. First set of optimizations and implementation. --- .../DelegateVsComparerBenchmark.cs | 26 +- .../ListVsLinkedListBenchmark.cs | 279 --------------- .../ListVsLinkedListBenchmarks.cs | 262 ++++++++++++++ Recyclable.Collections.Benchmarks/Program.cs | 10 +- .../RecyclableCollectionsBenchmarks.Array.cs | 52 +++ .../RecyclableCollectionsBenchmarks.List.cs | 83 +++++ ...llectionsBenchmarks.RecyclableArrayList.cs | 97 ++++++ ...bleCollectionsBenchmarks.RecyclableList.cs | 90 +++++ Recyclable.Collections/ListExtensions.cs | 8 +- Recyclable.Collections/RecyclableArrayList.cs | 220 ++++++++++++ Recyclable.Collections/RecyclableBlockList.cs | 327 ------------------ Recyclable.Collections/RecyclableDefaults.cs | 1 + Recyclable.Collections/RecyclableList.cs | 198 ++++++----- .../RecyclableListExtensions.cs | 159 +++++---- Recyclable.Collections/RecyclableQueue.cs | 2 +- ...stTests.cs => RecyclableArrayListTests.cs} | 26 +- 16 files changed, 1040 insertions(+), 800 deletions(-) delete mode 100644 Recyclable.Collections.Benchmarks/ListVsLinkedListBenchmark.cs create mode 100644 Recyclable.Collections.Benchmarks/ListVsLinkedListBenchmarks.cs create mode 100644 Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.Array.cs create mode 100644 Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs create mode 100644 Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs create mode 100644 Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs create mode 100644 Recyclable.Collections/RecyclableArrayList.cs delete mode 100644 Recyclable.Collections/RecyclableBlockList.cs rename Recyclable.CollectionsTests/{RecyclableBlockListTests.cs => RecyclableArrayListTests.cs} (86%) diff --git a/Recyclable.Collections.Benchmarks/DelegateVsComparerBenchmark.cs b/Recyclable.Collections.Benchmarks/DelegateVsComparerBenchmark.cs index 904f509..2eb4204 100644 --- a/Recyclable.Collections.Benchmarks/DelegateVsComparerBenchmark.cs +++ b/Recyclable.Collections.Benchmarks/DelegateVsComparerBenchmark.cs @@ -84,7 +84,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark(Baseline = true)] + //[Benchmark(Baseline = true)] public void Comparer() { TestWithComparer(_comparer); @@ -99,7 +99,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark] + //[Benchmark] public void Func() { TestWithFunc((x, y) => (x - y) switch @@ -110,7 +110,7 @@ namespace Recyclable.Collections.Benchmarks }); } - [Benchmark] + //[Benchmark] public void StaticFunc() { TestWithFunc(static (x, y) => (x - y) switch @@ -130,7 +130,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark] + //[Benchmark] public void Delegate() { TestWithDelegate((x, y) => (x - y) switch @@ -141,7 +141,7 @@ namespace Recyclable.Collections.Benchmarks }); } - [Benchmark] + //[Benchmark] public void ListOfFuncs() { const long x = 2, y = 5; @@ -153,7 +153,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark] + //[Benchmark] public void ListOfLocalFuncs() { const long x = 2, y = 5; @@ -165,7 +165,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark] + //[Benchmark] public void ListOfComparers() { const long x = 2, y = 5; @@ -177,7 +177,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark] + //[Benchmark] public void ListOfClasses() { const long x = 2, y = 5; @@ -189,7 +189,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark] + //[Benchmark] public void ListOfStructs() { const long x = 2, y = 5; @@ -201,7 +201,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark] + //[Benchmark] public void ListOfBoxedStructs() { const long x = 2, y = 5; @@ -213,7 +213,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark] + //[Benchmark] public void ListOfUnboxedStructs() { const long x = 2, y = 5; @@ -225,7 +225,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark] + //[Benchmark] public void ListOfUnboxedReadOnlyStructs() { const long x = 2, y = 5; @@ -237,7 +237,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark] + //[Benchmark] public void ListOfDelegates() { const long x = 2, y = 5; diff --git a/Recyclable.Collections.Benchmarks/ListVsLinkedListBenchmark.cs b/Recyclable.Collections.Benchmarks/ListVsLinkedListBenchmark.cs deleted file mode 100644 index c43858e..0000000 --- a/Recyclable.Collections.Benchmarks/ListVsLinkedListBenchmark.cs +++ /dev/null @@ -1,279 +0,0 @@ -using BenchmarkDotNet.Attributes; - -namespace Recyclable.Collections.Benchmarks -{ - [MemoryDiagnoser] - public class ListVsLinkedListBenchmark - { - //[Params(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 128, 256, 512, 1024, 2048, 4096, 8192, 1_000_000)] - public int TestObjectCount = 1_000_000; - public int BlockSize => TestObjectCount switch - { - 0 => 1, - > 0 and <= 10 => TestObjectCount, - _ => TestObjectCount / 10 - }; - - private object[]? _testObjects; - - public ListVsLinkedListBenchmark() - { - _ = TestObjects; - } - - private object[] TestObjects => _testObjects ??= Enumerable.Range(1, TestObjectCount).Select(x => new object()).ToArray(); - - private static void DoNothing(T obj) - { - _ = obj; - } - - [Benchmark] - public void Array() - { - var data = TestObjects; - var dataCount = TestObjectCount; - var list = new object[dataCount]; - for (var i = 0; i < dataCount; i++) - { - list[i] = data[i]; - } - - for (var i = 0; i < list.LongLength; i++) - { - DoNothing(list[i]); - } - } - - [Benchmark] - public void RecyclableBlockList() - { - var data = TestObjects; - var dataCount = TestObjectCount; - using var list = new RecyclableBlockList(initialCapacity: dataCount); - for (var i = 0; i < dataCount; i++) - { - list.Add(data[i]); - } - - for (var i = 0; i < list.LongCount; i++) - { - DoNothing(list[i]); - } - - list.Clear(); - } - - [Benchmark(Baseline = true)] - public void RecyclableList() - { - var data = TestObjects; - var dataCount = TestObjectCount; - using var list = new RecyclableList(BlockSize, initialCapacity: dataCount); - for (var i = 0; i < dataCount; i++) - { - list.Add(data[i]); - } - - for (var i = 0; i < list.LongCount; i++) - { - DoNothing(list[i]); - } - - list.Clear(); - } - - [Benchmark] - public void List() - { - var data = TestObjects; - var dataCount = TestObjectCount; - var list = new List(capacity: dataCount); - for (var i = 0; i < dataCount; i++) - { - list.Add(data[i]); - } - - for (var i = 0; i < list.Count; i++) - { - DoNothing(list[i]); - } - - list.Clear(); - } - - //[Benchmark] - public void LinkedList() - { - var data = TestObjects; - var dataCount = TestObjectCount; - var list = new LinkedList(); - for (var i = 0; i < dataCount; i++) - { - _ = list.AddLast(data[i]); - } - - foreach (var node in list) - { - DoNothing(node); - } - - list.Clear(); - } - - //[Benchmark] - public void RecyclableQueue() - { - var data = TestObjects; - var dataCount = TestObjectCount; - var list = new RecyclableQueue(BlockSize); - for (var i = 0; i < dataCount; i++) - { - list.Enqueue(data[i]); - } - - while (list.LongCount > 0) - { - DoNothing(list.Dequeue()); - } - - list.Clear(); - } - - //[Benchmark] - public void Queue() - { - var data = TestObjects; - var dataCount = TestObjectCount; - var list = new Queue(capacity: dataCount); - for (var i = 0; i < dataCount; i++) - { - list.Enqueue(data[i]); - } - - while (list.Count > 0) - { - DoNothing(list.Dequeue()); - } - - list.Clear(); - } - - //[Benchmark] - public void PriorityQueue() - { - var data = TestObjects; - var dataCount = TestObjectCount; - var list = new PriorityQueue(initialCapacity: dataCount); - for (var i = 0; i < dataCount; i++) - { - list.Enqueue(data[i], i); - } - - while (list.Count > 0) - { - DoNothing(list.Dequeue()); - } - - list.Clear(); - } - - //[Benchmark] - public void RecyclableSortedList_InUpdateMode() - { - var data = TestObjects; - var dataCount = TestObjectCount; - var list = new RecyclableSortedList(BlockSize); - list.BeginUpdate(); - for (var i = 0; i < dataCount; i++) - { - list.Add(i, data[i]); - } - - list.EndUpdate(); - - for (var i = 0; i < list.LongCount; i++) - { - DoNothing(list[i]); - } - - list.Clear(); - } - - //[Benchmark] - public void RecyclableSortedList() - { - var data = TestObjects; - var dataCount = TestObjectCount; - var list = new RecyclableSortedList(BlockSize); - for (var i = 0; i < dataCount; i++) - { - list.Add(i, data[i]); - } - - for (var i = 0; i < list.LongCount; i++) - { - DoNothing(list[i]); - } - - list.Clear(); - } - - //[Benchmark] - public void SortedList() - { - var data = TestObjects; - var dataCount = TestObjectCount; - var list = new SortedList(); - for (var i = 0; i < dataCount; i++) - { - list.Add(i, data[i]); - } - - for (var i = 0; i < list.Count; i++) - { - DoNothing(list[i]); - } - - list.Clear(); - } - - //[Benchmark] - public void RecyclableStack() - { - var data = TestObjects; - var dataCount = TestObjectCount; - var list = new RecyclableStack(BlockSize); - for (var i = 0; i < dataCount; i++) - { - list.Push(data[i]); - } - - while (list.LongCount > 0) - { - DoNothing(list.Pop()); - } - - list.Clear(); - } - - //[Benchmark] - public void Stack() - { - var data = TestObjects; - var dataCount = TestObjectCount; - var list = new Stack(); - for (var i = 0; i < dataCount; i++) - { - list.Push(data[i]); - } - - while (list.Count > 0) - { - DoNothing(list.Pop()); - } - - list.Clear(); - } - } -} diff --git a/Recyclable.Collections.Benchmarks/ListVsLinkedListBenchmarks.cs b/Recyclable.Collections.Benchmarks/ListVsLinkedListBenchmarks.cs new file mode 100644 index 0000000..108f96b --- /dev/null +++ b/Recyclable.Collections.Benchmarks/ListVsLinkedListBenchmarks.cs @@ -0,0 +1,262 @@ +namespace Recyclable.Collections.Benchmarks +{ + public class BenchmarkBase + { + //[Params(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 128, 256, 512, 1024, 2048, 4096, 8192, 1_000_000)] + public const int TestObjectCount = 1_000_000; + public static int BlockSize = TestObjectCount switch + { + 0 => 1, + > 0 and <= 10 => TestObjectCount, + _ => TestObjectCount / 10 + }; + + protected object[]? _testObjects; + protected object[] TestObjects => _testObjects ??= Enumerable.Range(1, TestObjectCount).Select(x => new object()).ToArray(); + + protected static void DoNothing(T obj) + { + _ = obj; + } + + public BenchmarkBase() + { + _ = TestObjects; + } + } + + //[MemoryDiagnoser] + //public class ListVsLinkedListBenchmarks + //{ + // [Benchmark] + // public void RecyclableBlockList() + // { + // var data = TestObjects; + // var dataCount = TestObjectCount; + // using var list = new RecyclableBlockList(initialCapacity: dataCount); + // for (long i = 0; i < dataCount; i++) + // { + // list.Add(data[i]); + // } + + // for (long i = 0; i < list.LongCount; i++) + // { + // DoNothing(list[i]); + // } + + // list.Clear(); + // } + + // [Benchmark(Baseline = true)] + // public void RecyclableList() + // { + // var data = TestObjects; + // long dataCount = TestObjectCount; + // using var list = new RecyclableList(BlockSize, totalItemsCount: dataCount); + // for (long i = 0; i < dataCount; i++) + // { + // list.Add(data[i]); + // } + + // for (long i = 0; i < list.LongCount; i++) + // { + // DoNothing(list[i]); + // } + + // list.Clear(); + // } + + // //[Benchmark] + // public void List() + // { + // var data = TestObjects; + // var dataCount = TestObjectCount; + // var list = new List(capacity: dataCount); + // for (var i = 0; i < dataCount; i++) + // { + // list.Add(data[i]); + // } + + // for (var i = 0; i < list.Count; i++) + // { + // DoNothing(list[i]); + // } + + // list.Clear(); + // } + + // //[Benchmark] + // public void LinkedList() + // { + // var data = TestObjects; + // var dataCount = TestObjectCount; + // var list = new LinkedList(); + // for (var i = 0; i < dataCount; i++) + // { + // _ = list.AddLast(data[i]); + // } + + // foreach (var node in list) + // { + // DoNothing(node); + // } + + // list.Clear(); + // } + + // //[Benchmark] + // public void RecyclableQueue() + // { + // var data = TestObjects; + // var dataCount = TestObjectCount; + // var list = new RecyclableQueue(BlockSize); + // for (var i = 0; i < dataCount; i++) + // { + // list.Enqueue(data[i]); + // } + + // while (list.LongCount > 0) + // { + // DoNothing(list.Dequeue()); + // } + + // list.Clear(); + // } + + // //[Benchmark] + // public void Queue() + // { + // var data = TestObjects; + // var dataCount = TestObjectCount; + // var list = new Queue(capacity: dataCount); + // for (var i = 0; i < dataCount; i++) + // { + // list.Enqueue(data[i]); + // } + + // while (list.Count > 0) + // { + // DoNothing(list.Dequeue()); + // } + + // list.Clear(); + // } + + // //[Benchmark] + // public void PriorityQueue() + // { + // var data = TestObjects; + // var dataCount = TestObjectCount; + // var list = new PriorityQueue(initialCapacity: dataCount); + // for (var i = 0; i < dataCount; i++) + // { + // list.Enqueue(data[i], i); + // } + + // while (list.Count > 0) + // { + // DoNothing(list.Dequeue()); + // } + + // list.Clear(); + // } + + // //[Benchmark] + // public void RecyclableSortedList_InUpdateMode() + // { + // var data = TestObjects; + // var dataCount = TestObjectCount; + // var list = new RecyclableSortedList(BlockSize); + // list.BeginUpdate(); + // for (var i = 0; i < dataCount; i++) + // { + // list.Add(i, data[i]); + // } + + // list.EndUpdate(); + + // for (var i = 0; i < list.LongCount; i++) + // { + // DoNothing(list[i]); + // } + + // list.Clear(); + // } + + // //[Benchmark] + // public void RecyclableSortedList() + // { + // var data = TestObjects; + // var dataCount = TestObjectCount; + // var list = new RecyclableSortedList(BlockSize); + // for (var i = 0; i < dataCount; i++) + // { + // list.Add(i, data[i]); + // } + + // for (var i = 0; i < list.LongCount; i++) + // { + // DoNothing(list[i]); + // } + + // list.Clear(); + // } + + // //[Benchmark] + // public void SortedList() + // { + // var data = TestObjects; + // var dataCount = TestObjectCount; + // var list = new SortedList(); + // for (var i = 0; i < dataCount; i++) + // { + // list.Add(i, data[i]); + // } + + // for (var i = 0; i < list.Count; i++) + // { + // DoNothing(list[i]); + // } + + // list.Clear(); + // } + + // //[Benchmark] + // public void RecyclableStack() + // { + // var data = TestObjects; + // var dataCount = TestObjectCount; + // var list = new RecyclableStack(BlockSize); + // for (var i = 0; i < dataCount; i++) + // { + // list.Push(data[i]); + // } + + // while (list.LongCount > 0) + // { + // DoNothing(list.Pop()); + // } + + // list.Clear(); + // } + + // //[Benchmark] + // public void Stack() + // { + // var data = TestObjects; + // var dataCount = TestObjectCount; + // var list = new Stack(); + // for (var i = 0; i < dataCount; i++) + // { + // list.Push(data[i]); + // } + + // while (list.Count > 0) + // { + // DoNothing(list.Pop()); + // } + + // list.Clear(); + // } + //} +} diff --git a/Recyclable.Collections.Benchmarks/Program.cs b/Recyclable.Collections.Benchmarks/Program.cs index e35ca2b..b55ba7a 100644 --- a/Recyclable.Collections.Benchmarks/Program.cs +++ b/Recyclable.Collections.Benchmarks/Program.cs @@ -1,8 +1,14 @@ using BenchmarkDotNet.Running; using Recyclable.Collections.Benchmarks; -BenchmarkRunner.Run(); +//BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(new[] { "-f*" }); + +BenchmarkRunner.Run(); + +//var test = new RecyclableCollectionsBenchmarks(); + + //BenchmarkRunner.Run(); //var tests = new ListVsLinkedListBenchmark(); -//tests.RecyclableBlockList(); +//tests.RecyclableList(); diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.Array.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.Array.cs new file mode 100644 index 0000000..b87abe3 --- /dev/null +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.Array.cs @@ -0,0 +1,52 @@ +using BenchmarkDotNet.Attributes; + +namespace Recyclable.Collections.Benchmarks +{ + [MemoryDiagnoser] + public partial class RecyclableCollectionsBenchmarks : BenchmarkBase + { + private readonly object[] _testArray; + + //[Benchmark(Baseline = true)] + public void Array_Create_WithCapacity() + { + DoNothing(new object[TestObjectCount]); + } + + //[Benchmark] + public void Array_GetItem() + { + var data = _testArray; + var dataCount = TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(data[i]); + } + } + + //[Benchmark] + public void Array_SetItem() + { + var data = _testArray; + var dataCount = TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + data[i] = TestObjects[i]; + } + } + + //[Benchmark] + public void Array_Count() + { + var data = _testArray; + DoNothing(data.Length); + } + + //[Benchmark] + public void Array_LongCount() + { + var data = _testArray; + DoNothing(data.LongLength); + } + } +} diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs new file mode 100644 index 0000000..9766fc0 --- /dev/null +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs @@ -0,0 +1,83 @@ +using BenchmarkDotNet.Attributes; + +namespace Recyclable.Collections.Benchmarks +{ + public partial class RecyclableCollectionsBenchmarks : BenchmarkBase + { + private readonly List _testList; + + [Benchmark(Baseline = true)] + public void List_Create() + { + var list = new List(); + DoNothing(list); + } + + [Benchmark] + public void List_Create_0Capacity() + { + var list = new List(0); + DoNothing(list); + } + + [Benchmark] + public void List_Create_WithCapacity() + { + var list = new List(TestObjectCount); + DoNothing(list); + } + + //[Benchmark] + public void List_Add() + { + var data = TestObjects; + var dataCount = TestObjectCount; + var list = new List(); + for (var i = 0; i < dataCount; i++) + { + list.Add(data[i]); + } + } + + //[Benchmark] + public void List_Add_WithCapacity() + { + var data = TestObjects; + var dataCount = TestObjectCount; + var list = new List(dataCount); + for (var i = 0; i < dataCount; i++) + { + list.Add(data[i]); + } + } + + //[Benchmark] + public void List_GetItem() + { + var data = _testList; + var dataCount = TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(data[i]); + } + } + + //[Benchmark] + public void List_SetItem() + { + var data = _testList; + var dataCount = TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + data[i] = TestObjects[i]; + } + } + + //[Benchmark] + public void List_Count() + { + var data = _testList; + DoNothing(data.Count); + } + } +} diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs new file mode 100644 index 0000000..400b3f0 --- /dev/null +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs @@ -0,0 +1,97 @@ +using BenchmarkDotNet.Attributes; + +namespace Recyclable.Collections.Benchmarks +{ + public partial class RecyclableCollectionsBenchmarks : BenchmarkBase + { + private readonly RecyclableArrayList _testRecyclableArrayList; + + public RecyclableCollectionsBenchmarks() + { + _testRecyclableArrayList = new(TestObjects, TestObjectCount); + _testRecyclableList = new(TestObjects, TestObjectCount); + _testArray = TestObjects.ToArray(); + _testList = new(TestObjects); + } + + ~RecyclableCollectionsBenchmarks() + { + _testRecyclableArrayList?.Dispose(); + _testRecyclableList?.Dispose(); + } + + [Benchmark] + public void RecyclableArrayList_Create() + { + using var list = new RecyclableArrayList(); + DoNothing(list); + } + + [Benchmark] + public void RecyclableArrayList_Create_0Capacity() + { + using var list = new RecyclableArrayList(0); + DoNothing(list); + } + + [Benchmark] + public void RecyclableArrayList_Create_WithCapacity() + { + using var list = new RecyclableArrayList(TestObjectCount); + DoNothing(list); + } + + //[Benchmark] + public void RecyclableArrayList_Add() + { + var data = TestObjects; + var dataCount = TestObjectCount; + using var list = new RecyclableArrayList(); + for (var i = 0; i < dataCount; i++) + { + list.Add(data[i]); + } + } + + //[Benchmark] + public void RecyclableArrayList_Add_WithCapacity() + { + var data = TestObjects; + var dataCount = TestObjectCount; + using var list = new RecyclableArrayList(dataCount); + for (var i = 0; i < dataCount; i++) + { + list.Add(data[i]); + } + } + + //[Benchmark] + public void RecyclableArrayList_GetItem() + { + var data = _testRecyclableArrayList; + var dataCount = TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(data[i]); + } + } + + //[Benchmark] + public void RecyclableArrayList_SetItem() + { + var data = _testRecyclableArrayList; + var dataCount = TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + data[i] = TestObjects[i]; + } + } + + //[Benchmark] + public void RecyclableArrayList_Count() + { + var data = _testRecyclableArrayList; + DoNothing(data.Count); + } + } +} diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs new file mode 100644 index 0000000..57c760e --- /dev/null +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs @@ -0,0 +1,90 @@ +using BenchmarkDotNet.Attributes; + +namespace Recyclable.Collections.Benchmarks +{ + public partial class RecyclableCollectionsBenchmarks : BenchmarkBase + { + private readonly RecyclableList _testRecyclableList; + + [Benchmark] + public void RecyclableList_Create() + { + using var list = new RecyclableList(); + DoNothing(list); + } + + [Benchmark] + public void RecyclableList_Create_0Capacity() + { + using var list = new RecyclableList(totalItemsCount: 0); + DoNothing(list); + } + + [Benchmark] + public void RecyclableList_Create_WithCapacity() + { + using var list = new RecyclableList(totalItemsCount: TestObjectCount); + DoNothing(list); + } + + //[Benchmark] + public void RecyclableList_Add() + { + var data = TestObjects; + var dataCount = TestObjectCount; + using var list = new RecyclableList(); + for (var i = 0; i < dataCount; i++) + { + list.Add(data[i]); + } + } + + //[Benchmark] + public void RecyclableList_Add_WithCapacity() + { + var data = TestObjects; + var dataCount = TestObjectCount; + using var list = new RecyclableList(dataCount); + for (var i = 0; i < dataCount; i++) + { + list.Add(data[i]); + } + } + + //[Benchmark] + public void RecyclableList_GetItem() + { + var data = _testRecyclableList; + var dataCount = TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(data[i]); + } + } + + //[Benchmark] + public void RecyclableList_SetItem() + { + var data = _testRecyclableList; + var dataCount = TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + data[i] = TestObjects[i]; + } + } + + //[Benchmark] + public void RecyclableList_Count() + { + var data = _testRecyclableList; + DoNothing(data.Count); + } + + //[Benchmark] + public void RecyclableList_LongCount() + { + var data = _testRecyclableList; + DoNothing(data.LongCount); + } + } +} diff --git a/Recyclable.Collections/ListExtensions.cs b/Recyclable.Collections/ListExtensions.cs index 58f034b..f9361d7 100644 --- a/Recyclable.Collections/ListExtensions.cs +++ b/Recyclable.Collections/ListExtensions.cs @@ -7,10 +7,10 @@ namespace Recyclable.Collections public static class ListExtensions { [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool Contains(this RecyclableBlockList arrays, T item) => arrays.Any(x => x.Contains(item)); + public static bool Contains(this RecyclableArrayList arrays, T item) => arrays.Any(x => x.Contains(item)); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void CopyTo(this RecyclableBlockList arrays, long startingIndex, int blockSize, int lastBlockSize, T[] destinationArray, int destinationArrayIndex) + public static void CopyTo(this RecyclableArrayList arrays, long startingIndex, int blockSize, int lastBlockSize, T[] destinationArray, int destinationArrayIndex) { Span arrayMemory = destinationArray.AsSpan(); arrayMemory = arrayMemory[destinationArrayIndex..]; @@ -49,7 +49,7 @@ namespace Recyclable.Collections } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static IEnumerable Enumerate(this RecyclableBlockList arrays, int chunkSize, long totalCount) + public static IEnumerable Enumerate(this RecyclableArrayList arrays, int chunkSize, long totalCount) { long currentCount = 0; for (var arrayIdx = 0; (arrayIdx < arrays.Count) && (currentCount < totalCount); arrayIdx++) @@ -104,7 +104,7 @@ namespace Recyclable.Collections } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static long LongIndexOf(this RecyclableBlockList arrays, int blockSize, T item, IEqualityComparer comparer) + public static long LongIndexOf(this RecyclableArrayList arrays, int blockSize, T item, IEqualityComparer comparer) { for (var arrayIdx = 0; arrayIdx < arrays.Count; arrayIdx++) { diff --git a/Recyclable.Collections/RecyclableArrayList.cs b/Recyclable.Collections/RecyclableArrayList.cs new file mode 100644 index 0000000..f68b870 --- /dev/null +++ b/Recyclable.Collections/RecyclableArrayList.cs @@ -0,0 +1,220 @@ +using System.Collections; + +namespace Recyclable.Collections +{ + public class RecyclableArrayList : IEnumerable, IDisposable + { + private static readonly IEqualityComparer _equalityComparer = EqualityComparer.Default; + + protected T[] _memory; + + protected static T[] SetNewLength(in T[]? source, in int newSize) + { + var sourceLength = source?.Length ?? 0; + T[] newArray; + newArray = newSize.RentArrayFromPool(); + switch (newSize >= sourceLength) + { + case true: + if (sourceLength > 0) + { + Memory sourceMemory = new(source); + Memory newArrayMemory = new(newArray); + sourceMemory.CopyTo(newArrayMemory); + source!.ReturnToPool(); + } + + return newArray; + + case false: + Span sourceSpan = source.AsSpan(); + Span newArraySpan = newArray.AsSpan(); + sourceSpan = sourceSpan[..newSize]; + sourceSpan.CopyTo(newArraySpan); + source!.ReturnToPool(); + return newArray; + } + } + + protected void EnsureCapacity(in int requestedCapacity) + { + int newCapacity; + switch (_capacity > 0) + { + case true: + newCapacity = _capacity; + while (newCapacity < requestedCapacity) + { + newCapacity *= 2; + } + + break; + + case false: + newCapacity = requestedCapacity; + break; + } + + _memory = SetNewLength(_memory, newCapacity); + _capacity = _memory.Length; + } + +#pragma warning disable CS8618 // _memory will be initialized when the 1st item is added + public RecyclableArrayList() +#pragma warning restore CS8618 + { + } + +#pragma warning disable CS8618 // _memory will be initialized when the 1st item is added + public RecyclableArrayList(int initialCapacity) +#pragma warning restore CS8618 + { + // _memory will be initialized when the 1st item is added + if (initialCapacity > 0) + { + _memory = SetNewLength(_memory, initialCapacity); + _capacity = initialCapacity; + } + } + + public RecyclableArrayList(IEnumerable source, int initialCapacity = RecyclableDefaults.Capacity) + { + _memory = SetNewLength(_memory, initialCapacity); + _capacity = initialCapacity; + + foreach (var item in source) + { + Add(item); + } + } + + public T this[int index] + { + get => _memory[index]; + set => _memory[index] = value; + } + + internal int _capacity; + public int Capacity + { + get => _capacity; + set + { + if (_capacity != value) + { + _memory = SetNewLength(_memory, value); + _capacity = value; + } + } + } + public int Count { get; set; } + public bool IsReadOnly { get; } = false; + + public void Add(in T item) + { + int requestedCapacity = Count + 1; + if (_capacity < requestedCapacity) + { + EnsureCapacity(requestedCapacity); + } + + _memory[Count] = item; + Count++; + } + + public void Clear() + { + Count = 0; + } + + public bool Contains(T item) => _memory.Contains(item); + + public void CopyTo(T[] array, int arrayIndex) => _memory.CopyTo(array, arrayIndex); + + protected static IEnumerable Enumerate(RecyclableArrayList list) + { + for (var i = 0; i < list.Count; i++) + { + yield return list._memory[i]; + } + } + + public int IndexOf(T itemToFind) + { + var memory = _memory; + int itemCount = Count; + var equalityComparer = _equalityComparer; + for (var itemIdx = 0; itemIdx < itemCount; itemIdx++) + { + var item = memory[itemIdx]; + if (equalityComparer.Equals(item, itemToFind)) + { + return itemIdx; + } + } + + return -1; + } + + public void Insert(int index, T item) + { + int requestedCapacity = Count + 1; + if (Capacity < requestedCapacity) + { + EnsureCapacity(requestedCapacity); + } + + for (var toMoveIdx = Count - 1; toMoveIdx >= index; toMoveIdx--) + { + _memory[toMoveIdx + 1] = _memory[toMoveIdx]; + } + + _memory[index] = item; + Count++; + } + + public bool Remove(T itemToRemove) + { + var itemIdx = IndexOf(itemToRemove); + if (itemIdx >= 0) + { + RemoveAt(itemIdx); + return true; + } + + return false; + } + public void RemoveAt(int index) + { + int oldCount = Count; + int oldCountMinus1 = oldCount - 1; + if (index == oldCountMinus1) + { + Count--; + return; + } + + if (index >= oldCount) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + _memory.CopyItems(index, oldCountMinus1, ref _memory); + Count--; + } + + public IEnumerator GetEnumerator() => Enumerate(this).GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => Enumerate(this).GetEnumerator(); + + public void Dispose() + { + if (Capacity > 0) + { + Clear(); + Capacity = 0; + _memory.ReturnToPool(); + GC.SuppressFinalize(this); + } + } + } +} diff --git a/Recyclable.Collections/RecyclableBlockList.cs b/Recyclable.Collections/RecyclableBlockList.cs deleted file mode 100644 index ab50098..0000000 --- a/Recyclable.Collections/RecyclableBlockList.cs +++ /dev/null @@ -1,327 +0,0 @@ -using System.Collections; -using System.Runtime.CompilerServices; - -namespace Recyclable.Collections -{ - public class RecyclableBlockList : IEnumerable, IDisposable - { - private static readonly IEqualityComparer _equalityComparer = EqualityComparer.Default; - private bool _disposedValue; - - - private void CopyItems(long startingIndex, long count, ref T[] destArray, long offset = 1) - { - for (long destItemIndex = startingIndex, sourceItemIndex = offset; destItemIndex < startingIndex + count; destItemIndex++, sourceItemIndex++) - { - destArray[destItemIndex] = _memory[sourceItemIndex]; - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static T GetItem(in T[] arrays, in long index) => arrays[index]; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void InternalRemoveAt(long index) - { - long oldCount = LongCount; - long oldCountMinus1 = oldCount - 1; - if (index == oldCountMinus1) - { - LongCount--; - return; - } - - if (index >= oldCount) - { - throw new ArgumentOutOfRangeException(nameof(index)); - } - - CopyItems(index, oldCountMinus1, ref _memory); - - LongCount--; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static long LongIndexOf(in T[] memory, in long itemCount, T? itemToFind, IEqualityComparer equalityComparer) - { - for (var itemIdx = 0; itemIdx < itemCount; itemIdx++) - { - var item = memory[itemIdx]; - if (equalityComparer.Equals(item, itemToFind)) - { - return itemIdx; - } - } - - return -1; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void SetItem(in T[] arrays, in long index, in T value) => arrays[index] = value; - - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private T[] SetNewLength(in long newSize) - { - if (newSize == _capacity) - { - return _memory; - } - - var source = _memory; - var sourceLength = source.LongLength; - var is32bit = newSize <= int.MaxValue; - T[] newArray; - if (is32bit) - { - var newSize32bit = (int)newSize; - newArray = newSize32bit.RentArrayFromPool(); - if (newSize32bit >= sourceLength) - { - if (sourceLength > 0) - { - Memory sourceMemory = new(source); - Memory newArrayMemory = new(newArray); - sourceMemory.CopyTo(newArrayMemory); - source.ReturnToPool(); - } - - return newArray; - } - else - { - Span sourceSpan = source.AsSpan(); - Span newArraySpan = newArray.AsSpan(); - sourceSpan = sourceSpan[..newSize32bit]; - sourceSpan.CopyTo(newArraySpan); - } - } - else - { - newArray = newSize.RentArrayFromPool(); - if (newSize >= sourceLength) - { - source.CopyTo(newArray, 0); - } - else - { - CopyItems(0, newArray.LongLength, ref newArray, 0); - } - } - - source.ReturnToPool(); - return newArray; - } - - protected T[] _memory = Array.Empty(); - - public RecyclableBlockList(long initialCapacity = RecyclableDefaults.Capacity) - { - _memory = SetNewLength(initialCapacity); - _capacity = initialCapacity; - } - - public RecyclableBlockList(IEnumerable source, int initialCapacity = RecyclableDefaults.Capacity) - { - _memory = SetNewLength(initialCapacity); - _capacity = initialCapacity; - - foreach (var item in source) - { - Add(item); - } - } - - public T this[long index] - { - get => _memory[index]; - set => _memory[index] = value; - } - - public T this[int index] - { - get => _memory[index]; - set => _memory[index] = value; - } - - private long _capacity; - public long Capacity - { - get => _capacity; - set - { - if (_capacity != value) - { - _memory = SetNewLength(value); - _capacity = value; - } - } - } - public int Count => (int)LongCount; - public long LongCount { get; set; } - public bool IsReadOnly { get; } = false; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Add(in T item) - { - //EnsureCapacity(LongCount + 1); - long requestedCapacity = LongCount + 1; - if (_capacity < requestedCapacity) - { - long newCapacity; - switch (_capacity > 0) - { - case true: - newCapacity = _capacity * 2; - while (newCapacity < requestedCapacity) - { - newCapacity *= 2; - } - - break; - - case false: - newCapacity = requestedCapacity; - break; - } - - _memory = SetNewLength(newCapacity); - _capacity = newCapacity; - } - - _memory[LongCount] = item; - LongCount++; - } - - //[MethodImpl(MethodImplOptions.AggressiveInlining)] - //public void EnsureCapacity(long requestedCapacity) - //{ - // if (Capacity < requestedCapacity) - // { - // long newCapacity; - // switch (Capacity > 0) - // { - // case true: - // newCapacity = Capacity * 2; - // while (newCapacity < requestedCapacity) - // { - // newCapacity *= 2; - // } - - // break; - - // case false: - // newCapacity = requestedCapacity; - // break; - // } - - // _memory = SetNewLength(newCapacity); - // _capacity = newCapacity; - // } - //} - - public void Clear() - { - LongCount = 0; - } - - public bool Contains(T item) => _memory.Contains(item); - - public void CopyTo(T[] array, int arrayIndex) => _memory.CopyTo(array, arrayIndex); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public IEnumerable Enumerate() - { - for (var i = 0; i < LongCount; i++) - { - yield return _memory[i]; - } - } - - public int IndexOf(T item) => (int)LongIndexOf(_memory, LongCount, item, _equalityComparer); - - public void Insert(int index, T item) - { - long requestedCapacity = LongCount + 1; - if (Capacity < requestedCapacity) - { - long newCapacity; - switch (Capacity > 0) - { - case true: - newCapacity = Capacity * 2; - while (newCapacity < requestedCapacity) - { - newCapacity *= 2; - } - - break; - - case false: - newCapacity = requestedCapacity; - break; - } - - _memory = SetNewLength(newCapacity); - _capacity = newCapacity; - } - - for (var toMoveIdx = LongCount - 1; toMoveIdx >= index; toMoveIdx--) - { - _memory[toMoveIdx + 1] = _memory[toMoveIdx]; - } - - _memory[index] = item; - LongCount++; - } - - public long LongIndexOf(T item) => LongIndexOf(_memory, LongCount, item, _equalityComparer); - - public bool Remove(T item) - { - var itemIdx = LongIndexOf(item); - if (itemIdx >= 0) - { - RemoveAt(itemIdx); - return true; - } - - return false; - } - - public void RemoveAt(int index) => InternalRemoveAt(index); - public void RemoveAt(long index) => InternalRemoveAt(index); - - public IEnumerator GetEnumerator() => Enumerate().GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() => Enumerate().GetEnumerator(); - - protected virtual void Dispose(bool disposing) - { - if (!_disposedValue) - { - if (disposing) - { - Clear(); - _memory.ReturnToPool(); - Capacity = 0; - } - - _disposedValue = true; - } - } - - // override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources - // ~RecyclableList() - // { - // // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - // Dispose(disposing: false); - // } - - public void Dispose() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: true); - GC.SuppressFinalize(this); - } - } -} diff --git a/Recyclable.Collections/RecyclableDefaults.cs b/Recyclable.Collections/RecyclableDefaults.cs index 8c9b5d1..09e80ce 100644 --- a/Recyclable.Collections/RecyclableDefaults.cs +++ b/Recyclable.Collections/RecyclableDefaults.cs @@ -4,5 +4,6 @@ { public const int BlockSize = 10_240; public const int Capacity = 8; + public const int MinPooledArrayLength = 100; } } diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 0d3f93b..1c49bdd 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -1,51 +1,102 @@ -using System.Collections; -using System.Runtime.CompilerServices; +using System.Buffers; +using System.Collections; namespace Recyclable.Collections { public class RecyclableList : IDisposable, IList { private static readonly IEqualityComparer _equalityComparer = EqualityComparer.Default; + private const int _minPooledArraySize = 100; + private readonly int _blockSize; + private readonly RecyclableArrayList _memoryBlocks; - private readonly RecyclableBlockList _memoryBlocks; - private bool _disposedValue; - - public RecyclableList(int blockSize = RecyclableDefaults.BlockSize, long? initialCapacity = default) + private static void AddCapacity(RecyclableList list, RecyclableArrayList memoryBlocks, int additionalCapacity) { - BlockSize = blockSize; - initialCapacity ??= 8 * BlockSize; - int additionalArray = initialCapacity % BlockSize > 0 ? 1 : 0; - long targetCapacity = (initialCapacity.Value / BlockSize) + additionalArray; - _memoryBlocks = new(targetCapacity); - Capacity = initialCapacity.Value; - for (var blockIdx = 0; blockIdx < targetCapacity; blockIdx++) - { - _memoryBlocks.Add(BlockSize.RentArrayFromPool()); - } - - //MemoryBlocks.LongCount = MemoryBlocks.Capacity; + T[] newMemoryBlock = RentArrayFromPool(additionalCapacity); + memoryBlocks.Add(newMemoryBlock); + list.Capacity += additionalCapacity; } - public RecyclableList(IEnumerable source, int blockSize = RecyclableDefaults.BlockSize, long? initialCapacity = default) + private static void AddRange(RecyclableList destination, IEnumerable source) { - BlockSize = blockSize; - initialCapacity ??= 8 * BlockSize; - int additionalCapacity = initialCapacity % BlockSize > 0 ? 1 : 0; - long targetCapacity = (initialCapacity.Value / BlockSize) + additionalCapacity; - _memoryBlocks = new(targetCapacity); - Capacity = initialCapacity.Value; - for (var blockIdx = 0; blockIdx < targetCapacity; blockIdx++) - { - _memoryBlocks.Add(BlockSize.RentArrayFromPool()); - } - foreach (var item in source) { - Add(item); + destination.Add(item); } } - public int BlockSize { get; } + private static void RemoveAt(RecyclableList list, long index) + { + if (index == list.LongCount - 1) + { + list.LongCount--; + if (list.Capacity - list.LongCount == list._blockSize) + { + list.RemoveBlock(list.BlockCount - 1); + } + } + else + { + throw new NotSupportedException("Only removal of the last element is supported"); + } + } + + private static T[] RentArrayFromPool(int minSize) => (minSize >= _minPooledArraySize) + ? ArrayPool.Shared.Rent(minSize) + : new T[minSize]; + + private static void ReturnToPool(in T[] array) + { + if (array.LongLength is < _minPooledArraySize or > int.MaxValue) + { + return; + } + + ArrayPool.Shared.Return(array); + } + + private static void RemoveBlock(RecyclableList owner, RecyclableArrayList memoryBlocks, int blockSize, int index) + { + try + { + ReturnToPool(memoryBlocks[index]); + memoryBlocks.RemoveAt(index); + } + finally + { + owner.Capacity -= blockSize; + } + } + + private static RecyclableArrayList SetupMemoryBlocks(RecyclableList owner, int blockSize, long? totalItemsCount) + { + totalItemsCount ??= 8 * blockSize; + int additionalArray = totalItemsCount % blockSize > 0 ? 1 : 0; + int memoryBlockCount = (int)(totalItemsCount.Value / blockSize).LimitTo(int.MaxValue) + additionalArray; + RecyclableArrayList memoryBlocks = new(memoryBlockCount); + for (var blockIdx = 0; blockIdx < memoryBlockCount; blockIdx++) + { + memoryBlocks[blockIdx] = RentArrayFromPool(blockSize); + } + + memoryBlocks.Count = memoryBlockCount; + owner.Capacity = totalItemsCount.Value; + return memoryBlocks; + } + + + public RecyclableList(int blockSize = RecyclableDefaults.BlockSize, long? totalItemsCount = default) + { + _blockSize = blockSize; + _memoryBlocks = SetupMemoryBlocks(this, blockSize, totalItemsCount); + } + + public RecyclableList(IEnumerable source, int blockSize = RecyclableDefaults.BlockSize, long? totalItemsCount = default) + { + _blockSize = blockSize; + _memoryBlocks = SetupMemoryBlocks(this, blockSize, totalItemsCount); + AddRange(this, source); + } public T this[long index] { @@ -59,23 +110,22 @@ namespace Recyclable.Collections set => _memoryBlocks[index / _blockSize][index % _blockSize] = value; } - public long Capacity { get; set; } + public long Capacity { get; protected set; } public int Count => (int)LongCount; public long LongCount { get; set; } public bool IsReadOnly { get; } = false; - public long BlockCount => _memoryBlocks.LongCount; + public int BlockCount => _memoryBlocks.Count; - [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Add(T item) { - if (LongCount == Capacity) + long oldCount = LongCount; + int blockSize = _blockSize; + if (oldCount == Capacity) { - T[] newMemoryBlock = BlockSize.RentArrayFromPool(); - _memoryBlocks.Add(newMemoryBlock); - Capacity += BlockSize; + AddCapacity(this, _memoryBlocks, blockSize); } - memoryBlocks[oldCount / blockSize][oldCount % blockSize] = item; + _memoryBlocks[(int)(oldCount / blockSize)][oldCount % blockSize] = item; LongCount++; } @@ -88,7 +138,7 @@ namespace Recyclable.Collections { try { - RemoveBlock(_memoryBlocks.Count - 1); + RemoveBlock(this, _memoryBlocks, _blockSize, _memoryBlocks.Count - 1); } catch (Exception) { @@ -106,81 +156,29 @@ namespace Recyclable.Collections public bool Contains(T item) => _memoryBlocks.Contains(item); - public void CopyTo(T[] array, int arrayIndex) => _memoryBlocks.CopyTo(0, BlockSize, (int)LongCount % BlockSize, array, arrayIndex); + public void CopyTo(T[] array, int arrayIndex) => _memoryBlocks.CopyTo(0, _blockSize, (int)LongCount % _blockSize, array, arrayIndex); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public IEnumerator GetEnumerator() => _memoryBlocks.Enumerate(BlockSize, LongCount).GetEnumerator(); + public IEnumerator GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); - public int IndexOf(T item) => (int)_memoryBlocks.LongIndexOf(BlockSize, item, _equalityComparer); + public int IndexOf(T item) => (int)_memoryBlocks.LongIndexOf(_blockSize, item, _equalityComparer); public void Insert(int index, T item) => throw new NotSupportedException(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public long LongIndexOf(T item) => _memoryBlocks.LongIndexOf(BlockSize, item, _equalityComparer); + public long LongIndexOf(T item) => _memoryBlocks.LongIndexOf(_blockSize, item, _equalityComparer); public bool Remove(T item) => throw new NotSupportedException(); public void RemoveAt(int index) => RemoveAt(this, index); public void RemoveAt(long index) => RemoveAt(this, index); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void RemoveAt(RecyclableList list, long index) - { - if (index == list.LongCount - 1) - { - list.LongCount--; - if (list.Capacity - list.LongCount == list.BlockSize) - { - list.RemoveBlock(list.BlockCount - 1); - } - } - else - { - throw new NotSupportedException("Only removal of the last element is supported"); - } - } - - IEnumerator IEnumerable.GetEnumerator() => _memoryBlocks.Enumerate(BlockSize, LongCount).GetEnumerator(); - - protected virtual void Dispose(bool disposing) - { - if (!_disposedValue) - { - if (disposing) - { - Clear(); - } - - _disposedValue = true; - } - } - - // override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources - // ~RecyclableList() - // { - // // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - // Dispose(disposing: false); - // } + IEnumerator IEnumerable.GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); public void Dispose() { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: true); + Clear(); GC.SuppressFinalize(this); } - public void RemoveBlock(long index) - { - try - { - _memoryBlocks[index].ReturnToPool(); - _memoryBlocks.RemoveAt(index); - } - finally - { - Capacity -= BlockSize; - LongCount -= BlockSize; - } - } + public void RemoveBlock(int index) => RemoveBlock(this, _memoryBlocks, _blockSize, index); } } diff --git a/Recyclable.Collections/RecyclableListExtensions.cs b/Recyclable.Collections/RecyclableListExtensions.cs index dbcde99..a027410 100644 --- a/Recyclable.Collections/RecyclableListExtensions.cs +++ b/Recyclable.Collections/RecyclableListExtensions.cs @@ -1,5 +1,4 @@ using System.Buffers; -using System.Buffers; using System.Runtime.CompilerServices; namespace Recyclable.Collections @@ -8,18 +7,25 @@ namespace Recyclable.Collections { private const int _minPooledArraySize = 100; - [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void CopyItems(this T[] memory, long startingIndex, long count, ref T[] destArray, long offset = 1) + { + for (long destItemIndex = startingIndex, sourceItemIndex = offset; destItemIndex < startingIndex + count; destItemIndex++, sourceItemIndex++) + { + destArray[destItemIndex] = memory[sourceItemIndex]; + } + } + public static void CopyTo(this RecyclableList arrays, long startingIndex, int blockSize, int lastBlockSize, T[] destinationArray, int destinationArrayIndex) { Span arrayMemory = destinationArray.AsSpan(); arrayMemory = arrayMemory[destinationArrayIndex..]; int startingArrayIndex = (int)(startingIndex / blockSize); startingIndex %= blockSize; - if (arrays.Count > 0) + if (arrays.LongCount > 0) { ReadOnlySpan sourceMemory = arrays[0].AsSpan(); var maxToCopy = (int)Math.Min(blockSize - startingIndex, arrayMemory.Length); - if (arrays.Count == 1) + if (arrays.LongCount == 1) { maxToCopy = (int)Math.Min(maxToCopy, lastBlockSize); } @@ -47,91 +53,122 @@ namespace Recyclable.Collections } } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool Contains(this RecyclableList arrays, T item) => arrays.Any(x => x.Contains(item)); + //[MethodImpl(MethodImplOptions.AggressiveInlining)] + //public static bool Contains(this RecyclableList arrays, T item) => arrays.Any(x => x.Contains(item)); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static IEnumerable Enumerate(this RecyclableList arrays, int chunkSize, long totalCount) + //public static IEnumerable Enumerate(this RecyclableList arrays, int chunkSize, long totalCount) + //{ + // long currentCount = 0; + // for (var arrayIdx = 0; arrayIdx < arrays.Count; arrayIdx++) + // { + // var array = arrays[arrayIdx]; + // currentCount += chunkSize; + // switch (currentCount < totalCount) + // { + // case true: + // for (var valueIdx = 0; valueIdx < chunkSize; valueIdx++) + // { + // yield return array[valueIdx]; + // } + + // break; + + // case false: + // var partialCount = (int)(totalCount % chunkSize); + // int maxCount = partialCount > 0 ? partialCount : chunkSize; + // for (var valueIdx = 0; valueIdx < maxCount; valueIdx++) + // { + // yield return array[valueIdx]; + // } + + // break; + // } + // } + //} + + //[MethodImpl(MethodImplOptions.AggressiveInlining)] + //public static T LimitTo(this T value, T maxValue, IComparer comparer) + // => comparer.Compare(value, maxValue) > 0 ? maxValue : value; + + //[MethodImpl(MethodImplOptions.AggressiveInlining)] + public static long LongIndexOf(this RecyclableList arrays, long itemsCount, T itemToFind, IEqualityComparer comparer) { - long currentCount = 0; - for (var arrayIdx = 0; arrayIdx < arrays.Count; arrayIdx++) + for (var itemIdx = 0L; itemIdx < itemsCount; itemIdx++) { - var array = arrays[arrayIdx]; - currentCount += chunkSize; - switch (currentCount < totalCount) + var item = arrays[itemIdx]; + if (comparer.Equals(itemToFind, item)) { - case true: - for (var valueIdx = 0; valueIdx < chunkSize; valueIdx++) - { - yield return array[valueIdx]; - } - - break; - - case false: - var partialCount = (int)(totalCount % chunkSize); - int maxCount = partialCount > 0 ? partialCount : chunkSize; - for (var valueIdx = 0; valueIdx < maxCount; valueIdx++) - { - yield return array[valueIdx]; - } - - break; + return itemIdx; } + + itemIdx++; } + + return -1; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T LimitTo(this T value, T maxValue, IComparer comparer) - => comparer.Compare(value, maxValue) > 0 ? maxValue : value; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static long LongIndexOf(this RecyclableList arrays, int blockSize, T item, IEqualityComparer comparer) + public static long LongIndexOf(this RecyclableArrayList arrays, int itemsCount, T itemToFind, IEqualityComparer comparer) { - for (var arrayIdx = 0; arrayIdx < arrays.Count; arrayIdx++) + for (var itemIdx = 0; itemIdx < itemsCount; itemIdx++) { - var array = arrays[arrayIdx]; - Span arrayMemory = array.AsSpan(); - for (int memoryIdx = 0; memoryIdx < arrayMemory.Length; memoryIdx++) + var item = arrays[itemIdx]; + if (comparer.Equals(itemToFind, item)) { - if (comparer.Equals(arrayMemory[memoryIdx], item)) - { - return (arrayIdx * (long)blockSize) + memoryIdx; - } + return itemIdx; } } return -1; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RecyclableList ToRecyclableList(this IList values, int blockSize = RecyclableDefaults.BlockSize) => new(values, blockSize); + //[MethodImpl(MethodImplOptions.AggressiveInlining)] + //public static RecyclableList ToRecyclableList(this IList values, int blockSize = RecyclableDefaults.BlockSize) => new(values, blockSize); + + //[MethodImpl(MethodImplOptions.AggressiveInlining)] + //public static RecyclableList ToRecyclableList(this IEnumerable values, int blockSize = RecyclableDefaults.BlockSize) => new(values, blockSize); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RecyclableList ToRecyclableList(this IEnumerable values, int blockSize = RecyclableDefaults.BlockSize) => new(values, blockSize); + public static T[] RentArrayFromPool(this int minSize) => (minSize >= _minPooledArraySize) + ? ArrayPool.Shared.Rent(minSize) + : new T[minSize]; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T[] RentArrayFromPool(this int minSize) => RentArrayFromPool((long)minSize); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T[] RentArrayFromPool(this long minSize) - { - return minSize switch - { - < _minPooledArraySize => new T[minSize], - <= int.MaxValue => ArrayPool.Shared.Rent((int)minSize), - _ => new T[minSize] - }; - } + public static T[] RentArrayFromPool(this long minSize) => (minSize is >= _minPooledArraySize and <= int.MaxValue) + ? ArrayPool.Shared.Rent((int)minSize) + : new T[minSize]; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void ReturnToPool(this T[] array) { - long arrayLength = array.LongLength; - if (arrayLength is >= _minPooledArraySize and <= int.MaxValue) + if (array.LongLength is > _minPooledArraySize and <= int.MaxValue) { ArrayPool.Shared.Return(array); } } + + //public static void AddRange(this RecyclableList destination, IEnumerable source) + //{ + // foreach (var item in source) + // { + // destination.Add(item); + // } + //} + + public static long LimitTo(this long value, int limit) + => (value <= limit) ? value : limit; + + public static int IndexOf(this T[] memory, int itemCount, T? itemToFind, IEqualityComparer equalityComparer) + { + for (var itemIdx = 0; itemIdx < itemCount; itemIdx++) + { + var item = memory[itemIdx]; + if (equalityComparer.Equals(item, itemToFind)) + { + return itemIdx; + } + } + + return -1; + } } } diff --git a/Recyclable.Collections/RecyclableQueue.cs b/Recyclable.Collections/RecyclableQueue.cs index 0a70082..29c68cf 100644 --- a/Recyclable.Collections/RecyclableQueue.cs +++ b/Recyclable.Collections/RecyclableQueue.cs @@ -8,7 +8,7 @@ namespace Recyclable.Collections private static readonly IEqualityComparer _equalityComparer = EqualityComparer.Default; private bool _disposedValue; - private RecyclableBlockList Memory { get; } + private RecyclableArrayList Memory { get; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static T Dequeue(RecyclableQueue queue) diff --git a/Recyclable.CollectionsTests/RecyclableBlockListTests.cs b/Recyclable.CollectionsTests/RecyclableArrayListTests.cs similarity index 86% rename from Recyclable.CollectionsTests/RecyclableBlockListTests.cs rename to Recyclable.CollectionsTests/RecyclableArrayListTests.cs index 514d0fe..6390a78 100644 --- a/Recyclable.CollectionsTests/RecyclableBlockListTests.cs +++ b/Recyclable.CollectionsTests/RecyclableArrayListTests.cs @@ -3,7 +3,7 @@ using Recyclable.Collections; namespace Recyclable.CollectionsTests { - public class RecyclableBlockListTests + public class RecyclableArrayListTests { private readonly static IEnumerable _testData = Enumerable.Range(1, 20); @@ -12,13 +12,13 @@ namespace Recyclable.CollectionsTests { // Prepare var testData = _testData.ToArray(); - var list = new RecyclableBlockList(); + var list = new RecyclableArrayList(); // Act foreach (var item in testData) { list.Add(item); - _ = list.LongCount.Should().Be(item); + _ = list.Count.Should().Be(item); } // Validate @@ -33,13 +33,13 @@ namespace Recyclable.CollectionsTests { // Prepare var testData = _testData.ToArray(); - var list = new RecyclableBlockList(); + var list = new RecyclableArrayList(); // Act foreach (var item in testData) { list.Insert(0, item); - _ = list.LongCount.Should().Be(item); + _ = list.Count.Should().Be(item); } // Validate @@ -54,7 +54,7 @@ namespace Recyclable.CollectionsTests { // Prepare var testData = _testData.ToArray(); - var list = new RecyclableBlockList(testData); + var list = new RecyclableArrayList(testData); // Act & Validate foreach (var item in testData) @@ -72,7 +72,7 @@ namespace Recyclable.CollectionsTests { // Prepare var testData = _testData.ToArray(); - var list = new RecyclableBlockList(testData); + var list = new RecyclableArrayList(testData); // Act & Validate for (var deleted = 1; deleted <= testData.LongLength; deleted++) @@ -94,7 +94,7 @@ namespace Recyclable.CollectionsTests var testData = _testData.ToArray(); // Act & Validate - var list = new RecyclableBlockList(testData); + var list = new RecyclableArrayList(testData); for (var deleted = 1; deleted <= testData.LongLength; deleted++) { // Act @@ -112,7 +112,7 @@ namespace Recyclable.CollectionsTests { // Prepare var testData = _testData.ToArray(); - var list = new RecyclableBlockList(testData); + var list = new RecyclableArrayList(testData); // Act & Validate for (var deleted = 1; deleted <= testData.LongLength; deleted++) @@ -135,13 +135,13 @@ namespace Recyclable.CollectionsTests { // Prepare var testData = _testData.ToArray(); - var list = new RecyclableBlockList(testData); + var list = new RecyclableArrayList(testData); // Act & Validate for (var deleted = 1; deleted <= testData.LongLength; deleted++) { // Act - list.RemoveAt(list.LongCount - 1); + list.RemoveAt(list.Count - 1); // Validate _ = list.Should().HaveCount(testData.Length - deleted) @@ -160,7 +160,7 @@ namespace Recyclable.CollectionsTests var testData = _testData.ToArray(); // Act - var list = new RecyclableBlockList(testData); + var list = new RecyclableArrayList(testData); // Validate _ = list.Should().HaveCount(testData.Length) @@ -173,7 +173,7 @@ namespace Recyclable.CollectionsTests { // Prepare var testData = _testData.ToArray(); - var list = new RecyclableBlockList(testData); + var list = new RecyclableArrayList(testData); // Act var yieldedItems = new List();