diff --git a/Recyclable.Collections.Benchmarks/ListVsLinkedListBenchmarks.cs b/Recyclable.Collections.Benchmarks/ListVsLinkedListBenchmarks.cs index 108f96b..52d8471 100644 --- a/Recyclable.Collections.Benchmarks/ListVsLinkedListBenchmarks.cs +++ b/Recyclable.Collections.Benchmarks/ListVsLinkedListBenchmarks.cs @@ -29,11 +29,11 @@ //public class ListVsLinkedListBenchmarks //{ // [Benchmark] - // public void RecyclableBlockList() + // public void RecyclableArrayList() // { // var data = TestObjects; // var dataCount = TestObjectCount; - // using var list = new RecyclableBlockList(initialCapacity: dataCount); + // using var list = new RecyclableArrayList(initialCapacity: dataCount); // for (long i = 0; i < dataCount; i++) // { // list.Add(data[i]); diff --git a/Recyclable.Collections.Benchmarks/Program.cs b/Recyclable.Collections.Benchmarks/Program.cs index b55ba7a..fdc21ac 100644 --- a/Recyclable.Collections.Benchmarks/Program.cs +++ b/Recyclable.Collections.Benchmarks/Program.cs @@ -5,7 +5,9 @@ using Recyclable.Collections.Benchmarks; BenchmarkRunner.Run(); -//var test = new RecyclableCollectionsBenchmarks(); +//var benchmark = new RecyclableCollectionsBenchmarks(); +//benchmark.RecyclableArrayList_Add(); +//benchmark.RecyclableArrayList_Add_WithCapacity(); //BenchmarkRunner.Run(); diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs index 9766fc0..e7f3324 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs @@ -6,28 +6,28 @@ namespace Recyclable.Collections.Benchmarks { private readonly List _testList; - [Benchmark(Baseline = true)] + //[Benchmark(Baseline = true)] public void List_Create() { var list = new List(); DoNothing(list); } - [Benchmark] + //[Benchmark] public void List_Create_0Capacity() { var list = new List(0); DoNothing(list); } - [Benchmark] + //[Benchmark] public void List_Create_WithCapacity() { var list = new List(TestObjectCount); DoNothing(list); } - //[Benchmark] + [Benchmark] public void List_Add() { var data = TestObjects; @@ -39,7 +39,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark] + [Benchmark(Baseline = true)] public void List_Add_WithCapacity() { var data = TestObjects; @@ -51,6 +51,23 @@ namespace Recyclable.Collections.Benchmarks } } + [Benchmark] + public void List_AddRange() + { + var data = TestObjects; + var list = new List(); + list.AddRange(data); + } + + [Benchmark] + public void List_AddRange_WithCapacity() + { + var data = TestObjects; + var dataCount = TestObjectCount; + var list = new List(dataCount); + list.AddRange(data); + } + //[Benchmark] public void List_GetItem() { diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs index 400b3f0..11a8a33 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs @@ -20,28 +20,28 @@ namespace Recyclable.Collections.Benchmarks _testRecyclableList?.Dispose(); } - [Benchmark] + //[Benchmark] public void RecyclableArrayList_Create() { using var list = new RecyclableArrayList(); DoNothing(list); } - [Benchmark] + //[Benchmark] public void RecyclableArrayList_Create_0Capacity() { using var list = new RecyclableArrayList(0); DoNothing(list); } - [Benchmark] + //[Benchmark] public void RecyclableArrayList_Create_WithCapacity() { using var list = new RecyclableArrayList(TestObjectCount); 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; @@ -65,6 +65,23 @@ namespace Recyclable.Collections.Benchmarks } } + [Benchmark] + public void RecyclableArrayList_AddRange() + { + var data = TestObjects; + using var list = new RecyclableArrayList(); + list.AddRange(data); + } + + [Benchmark] + public void RecyclableArrayList_AddRange_WithCapacity() + { + var data = TestObjects; + var dataCount = TestObjectCount; + using var list = new RecyclableArrayList(dataCount); + list.AddRange(data); + } + //[Benchmark] public void RecyclableArrayList_GetItem() { diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs index 57c760e..27ff894 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs @@ -6,28 +6,28 @@ namespace Recyclable.Collections.Benchmarks { private readonly RecyclableList _testRecyclableList; - [Benchmark] + //[Benchmark] public void RecyclableList_Create() { using var list = new RecyclableList(); DoNothing(list); } - [Benchmark] + //[Benchmark] public void RecyclableList_Create_0Capacity() { using var list = new RecyclableList(totalItemsCount: 0); DoNothing(list); } - [Benchmark] + //[Benchmark] public void RecyclableList_Create_WithCapacity() { using var list = new RecyclableList(totalItemsCount: TestObjectCount); DoNothing(list); } - //[Benchmark] + [Benchmark] public void RecyclableList_Add() { var data = TestObjects; @@ -39,7 +39,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark] + [Benchmark] public void RecyclableList_Add_WithCapacity() { var data = TestObjects; diff --git a/Recyclable.Collections/RecyclableArrayList.cs b/Recyclable.Collections/RecyclableArrayList.cs index f68b870..81ab4dc 100644 --- a/Recyclable.Collections/RecyclableArrayList.cs +++ b/Recyclable.Collections/RecyclableArrayList.cs @@ -1,4 +1,5 @@ -using System.Collections; +using System.Buffers; +using System.Collections; namespace Recyclable.Collections { @@ -36,7 +37,7 @@ namespace Recyclable.Collections } } - protected void EnsureCapacity(in int requestedCapacity) + protected ref int EnsureCapacity(in int requestedCapacity) { int newCapacity; switch (_capacity > 0) @@ -56,7 +57,9 @@ namespace Recyclable.Collections } _memory = SetNewLength(_memory, newCapacity); - _capacity = _memory.Length; + newCapacity = _memory.Length; + _capacity = newCapacity; + return ref _capacity; } #pragma warning disable CS8618 // _memory will be initialized when the 1st item is added @@ -107,6 +110,7 @@ namespace Recyclable.Collections } } } + public int Count { get; set; } public bool IsReadOnly { get; } = false; @@ -115,13 +119,48 @@ namespace Recyclable.Collections int requestedCapacity = Count + 1; if (_capacity < requestedCapacity) { - EnsureCapacity(requestedCapacity); + _ = EnsureCapacity(requestedCapacity); } _memory[Count] = item; Count++; } + public void AddRange(in T[] items) + { + int sourceItemsCount = items.Length; + int targetCapacity = Count + sourceItemsCount; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(targetCapacity); + } + + Span itemsSpan = items.AsSpan(); + for (int sourceItemIdx = 0, targetItemIdx = Count; sourceItemIdx < sourceItemsCount;) + { + _memory[targetItemIdx++] = itemsSpan[sourceItemIdx++]; + } + + Count = targetCapacity; + } + + public void AddRange(in List items) + { + var sourceItemsCount = items.Count; + var targetCapacity = Count + sourceItemsCount; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(targetCapacity); + } + + for (int sourceItemIdx = 0, targetItemIdx = Count; sourceItemIdx < sourceItemsCount;) + { + _memory[targetItemIdx++] = items[sourceItemIdx++]; + } + + Count = targetCapacity; + } + public void Clear() { Count = 0; @@ -159,9 +198,9 @@ namespace Recyclable.Collections public void Insert(int index, T item) { int requestedCapacity = Count + 1; - if (Capacity < requestedCapacity) + if (_capacity < requestedCapacity) { - EnsureCapacity(requestedCapacity); + _ = EnsureCapacity(requestedCapacity); } for (var toMoveIdx = Count - 1; toMoveIdx >= index; toMoveIdx--) diff --git a/Recyclable.Collections/RecyclableArrayList_CreateBenchmarks.png b/Recyclable.Collections/RecyclableArrayList_CreateBenchmarks.png new file mode 100644 index 0000000..ed3622a Binary files /dev/null and b/Recyclable.Collections/RecyclableArrayList_CreateBenchmarks.png differ diff --git a/Recyclable.CollectionsTests/RecyclableArrayListTests.cs b/Recyclable.CollectionsTests/RecyclableArrayListTests.cs index 6390a78..6fcc043 100644 --- a/Recyclable.CollectionsTests/RecyclableArrayListTests.cs +++ b/Recyclable.CollectionsTests/RecyclableArrayListTests.cs @@ -6,7 +6,7 @@ namespace Recyclable.CollectionsTests public class RecyclableArrayListTests { private readonly static IEnumerable _testData = Enumerable.Range(1, 20); - + [Fact] public void AddCountShouldSucceed() { @@ -28,6 +28,40 @@ namespace Recyclable.CollectionsTests .And.BeEquivalentTo(testData); } + [Fact] + public void AddRangeShouldAddItemsInCorrectOrderWhenSourceIsArray() + { + // Prepare + var testData = _testData.ToArray(); + var list = new RecyclableArrayList(); + + // Act + list.AddRange(testData); + + // Validate + _ = list.Capacity.Should().Be(20, "when capacity == 0, then we allocate as much memory as needed, only"); + _ = list.Should().HaveCount(testData.Length) + .And.ContainInConsecutiveOrder(testData) + .And.BeEquivalentTo(testData); + } + + [Fact] + public void AddRangeShouldAddItemsInCorrectOrderWhenSourceIsList() + { + // Prepare + var testData = _testData.ToList(); + var list = new RecyclableArrayList(); + + // Act + list.AddRange(testData); + + // Validate + _ = list.Capacity.Should().Be(20, "when capacity == 0, then we allocate as much memory as needed, only"); + _ = list.Should().HaveCount(testData.Count) + .And.ContainInConsecutiveOrder(testData) + .And.BeEquivalentTo(testData); + } + [Fact] public void InsertAtTheBeginningShouldMoveItems() {