diff --git a/Recyclable.Collections.Benchmarks/ListVsLinkedListBenchmarks.cs b/Recyclable.Collections.Benchmarks/ListVsLinkedListBenchmarks.cs index 579a4d8..d2d36f7 100644 --- a/Recyclable.Collections.Benchmarks/ListVsLinkedListBenchmarks.cs +++ b/Recyclable.Collections.Benchmarks/ListVsLinkedListBenchmarks.cs @@ -19,6 +19,7 @@ private RecyclableArrayList? _testObjectsAsRecyclableArrayList; protected RecyclableArrayList TestObjectsAsRecyclableArrayList => _testObjectsAsRecyclableArrayList ??= TestObjects.ToRecyclableArrayList(); + protected IEnumerable TestObjectsAsIEnumerable = Enumerable.Range(1, TestObjectCount).Select(static x => new object()); protected static void DoNothing(T obj) { diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.Array.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.Array.cs index c138fb9..2712b7c 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.Array.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.Array.cs @@ -25,7 +25,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark] + //[Benchmark] public void Array_GetItem() { var data = TestObjects; @@ -36,14 +36,14 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark] + //[Benchmark] public void Array_Count() { var data = _testArray; DoNothing(data.Length); } - [Benchmark] + //[Benchmark] public void Array_LongCount() { var data = _testArray; diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs index 766a3b3..83ee075 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 = false)] + //[Benchmark(Baseline = false)] public void List_Add_WithCapacity() { var data = TestObjects; @@ -51,7 +51,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark] + //[Benchmark] public void List_AddRangeWhenSourceIsArray() { var data = TestObjects; @@ -59,7 +59,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - [Benchmark] + //[Benchmark] public void List_AddRangeWhenSourceIsList() { var data = TestObjectsAsList; @@ -67,7 +67,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - [Benchmark] + //[Benchmark] public void List_AddRangeWhenSourceIsIList() { var data = (IList)TestObjectsAsList; @@ -78,12 +78,12 @@ namespace Recyclable.Collections.Benchmarks [Benchmark] public void List_AddRangeWhenSourceIsIEnumerable() { - var data = (IEnumerable)TestObjects; + var data = TestObjectsAsIEnumerable; var list = new List(); list.AddRange(data); } - [Benchmark(Baseline = false)] + //[Benchmark(Baseline = false)] public void List_AddRange_WithCapacity() { var data = TestObjects; @@ -92,7 +92,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - [Benchmark] + //[Benchmark] public void List_SetItem() { var data = TestObjectsAsList; @@ -103,7 +103,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark] + //[Benchmark] public void List_GetItem() { var data = TestObjectsAsList; @@ -114,7 +114,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark] + //[Benchmark] public void List_Count() { var data = _testList; diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs index a85ee9b..a405506 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableArrayList.cs @@ -44,7 +44,7 @@ namespace Recyclable.Collections.Benchmarks DoNothing(list); } - [Benchmark] + //[Benchmark] public void RecyclableArrayList_Add() { var data = TestObjects; @@ -56,7 +56,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark] + //[Benchmark] public void RecyclableArrayList_Add_WithCapacity() { var data = TestObjects; @@ -68,7 +68,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark] + //[Benchmark] public void RecyclableArrayList_AddRangeWhenSourceIsArray() { var data = TestObjects; @@ -76,7 +76,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - [Benchmark] + //[Benchmark] public void RecyclableArrayList_AddRangeWhenSourceIsList() { var data = TestObjectsAsList; @@ -84,7 +84,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - [Benchmark] + //[Benchmark] public void RecyclableArrayList_AddRangeWhenSourceIsIList() { var data = (IList)TestObjectsAsList; @@ -95,12 +95,12 @@ namespace Recyclable.Collections.Benchmarks [Benchmark] public void RecyclableArrayList_AddRangeWhenSourceIsIEnumerable() { - var data = (IEnumerable)TestObjects; + var data = TestObjectsAsIEnumerable; using var list = new RecyclableArrayList(); list.AddRange(data); } - [Benchmark] + //[Benchmark] public void RecyclableArrayList_AddRangeWhenSourceIsRecyclableArrayList() { var data = TestObjectsAsRecyclableArrayList; @@ -108,7 +108,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - [Benchmark] + //[Benchmark] public void RecyclableArrayList_AddRange_WithCapacity() { var data = TestObjects; @@ -117,7 +117,7 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - [Benchmark] + //[Benchmark] public void RecyclableArrayList_SetItem() { var data = TestObjectsAsRecyclableArrayList; @@ -128,7 +128,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark] + //[Benchmark] public void RecyclableArrayList_GetItem() { var data = TestObjectsAsRecyclableArrayList; @@ -139,7 +139,7 @@ namespace Recyclable.Collections.Benchmarks } } - [Benchmark] + //[Benchmark] public void RecyclableArrayList_Count() { var data = _testRecyclableArrayList; diff --git a/Recyclable.Collections/RecyclableArrayList.cs b/Recyclable.Collections/RecyclableArrayList.cs index d5af696..3591ecb 100644 --- a/Recyclable.Collections/RecyclableArrayList.cs +++ b/Recyclable.Collections/RecyclableArrayList.cs @@ -153,7 +153,7 @@ namespace Recyclable.Collections public bool IsReadOnly { get; } = false; - [MethodImpl(MethodImplOptions.AggressiveInlining)] + //[MethodImpl(MethodImplOptions.AggressiveInlining)] public void Add(T item) { int requestedCapacity = _count + 1; @@ -221,19 +221,90 @@ namespace Recyclable.Collections _count = targetCapacity; } - public void AddRange(IEnumerable source, int growByCount = 100) + public void AddRange(IEnumerable source, int growByCount = RecyclableDefaults.MinPooledArrayLength) { - ref int capacity = ref _capacity; - ref int count = ref _count; - foreach (var item in source) + if (source is T[] sourceArray) { - if (capacity == count) + AddRange(sourceArray); + return; + } + + if (source is List sourceList) + { + AddRange(sourceList); + return; + } + + if (source is RecyclableArrayList sourceRecyclableArrayList) + { + AddRange(sourceRecyclableArrayList); + return; + } + + if (source is IList sourceIList) + { + AddRange(sourceIList); + return; + } + + int capacity = _capacity; + int targetIndex = _count; + Span memorySpan; + if (source.TryGetNonEnumeratedCount(out var requiredAdditionalCapacity)) + { + _ = EnsureCapacity(targetIndex + requiredAdditionalCapacity); + memorySpan = new(_memory); + foreach (var item in source) { - _ = EnsureCapacity(capacity * growByCount); + memorySpan[targetIndex++] = item; } - Add(item); + _count = targetIndex; + return; } + + int i; + using var enumerator = source.GetEnumerator(); + + memorySpan = new(_memory); + if (enumerator.MoveNext()) + { + int available = capacity - targetIndex; + while (true) + { + if (targetIndex + growByCount > capacity) + { + capacity = EnsureCapacity(capacity + growByCount); + memorySpan = new(_memory); + available = capacity - targetIndex; + } + + for (i = 0; i < available; i++) + { + memorySpan[targetIndex++] = enumerator.Current; + if (!enumerator.MoveNext()) + { + break; + } + } + + if (i < available) + { + break; + } + } + } + + _count = targetIndex; + + //foreach (var item in source) + //{ + // if (capacity == count) + // { + // } + + // Add(item); + //} } public void Clear() diff --git a/Recyclable.Collections/RecyclableListExtensions.cs b/Recyclable.Collections/RecyclableListExtensions.cs index 959c07c..f07c7c2 100644 --- a/Recyclable.Collections/RecyclableListExtensions.cs +++ b/Recyclable.Collections/RecyclableListExtensions.cs @@ -5,7 +5,7 @@ namespace Recyclable.Collections { public static class RecyclableListExtensions { - private const int _minPooledArraySize = 100; + private const int _minPooledArraySize = RecyclableDefaults.MinPooledArrayLength; public static void CopyItems(this T[] memory, long startingIndex, long count, ref T[] destArray, long offset = 1) { diff --git a/Recyclable.CollectionsTests/RecyclableArrayListTests.cs b/Recyclable.CollectionsTests/RecyclableArrayListTests.cs index f482d9c..db99cc3 100644 --- a/Recyclable.CollectionsTests/RecyclableArrayListTests.cs +++ b/Recyclable.CollectionsTests/RecyclableArrayListTests.cs @@ -62,6 +62,22 @@ namespace Recyclable.CollectionsTests .And.BeEquivalentTo(testData); } + [Fact] + public void ConstructorShouldAddItemsInCorrectOrderWhenSourceIsIEnumerable() + { + // Prepare + var testData = _testData; + + // Act + var list = new RecyclableArrayList(testData); + + // Validate + _ = list.Capacity.Should().Be(32, "when we succeed in taking count without enumerating, then we allocate as much memory as needed, only"); + _ = list.Should().HaveCount(testData.Count()) + .And.ContainInConsecutiveOrder(testData) + .And.BeEquivalentTo(testData); + } + [Fact] public void InsertAtTheBeginningShouldMoveItems() {