diff --git a/Recyclable.Collections.Benchmarks/ListVsLinkedListBenchmarks.cs b/Recyclable.Collections.Benchmarks/ListVsLinkedListBenchmarks.cs index 52d8471..579a4d8 100644 --- a/Recyclable.Collections.Benchmarks/ListVsLinkedListBenchmarks.cs +++ b/Recyclable.Collections.Benchmarks/ListVsLinkedListBenchmarks.cs @@ -14,6 +14,12 @@ protected object[]? _testObjects; protected object[] TestObjects => _testObjects ??= Enumerable.Range(1, TestObjectCount).Select(x => new object()).ToArray(); + private List? _testObjectsAsList; + protected List TestObjectsAsList => _testObjectsAsList ??= TestObjects.ToList(); + + private RecyclableArrayList? _testObjectsAsRecyclableArrayList; + protected RecyclableArrayList TestObjectsAsRecyclableArrayList => _testObjectsAsRecyclableArrayList ??= TestObjects.ToRecyclableArrayList(); + protected static void DoNothing(T obj) { _ = obj; diff --git a/Recyclable.Collections.Benchmarks/Program.cs b/Recyclable.Collections.Benchmarks/Program.cs index 52450ac..c9eba31 100644 --- a/Recyclable.Collections.Benchmarks/Program.cs +++ b/Recyclable.Collections.Benchmarks/Program.cs @@ -3,8 +3,8 @@ using Recyclable.Collections.Benchmarks; //BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(new[] { "-f*" }); -BenchmarkRunner.Run(); -//BenchmarkRunner.Run(); +//BenchmarkRunner.Run(); +BenchmarkRunner.Run(); //var benchmark = new RecyclableCollectionsBenchmarks(); //benchmark.RecyclableArrayList_AddRange_WithCapacity(); diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.Array.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.Array.cs index b87abe3..d82524b 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.Array.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.Array.cs @@ -7,27 +7,17 @@ namespace Recyclable.Collections.Benchmarks { private readonly object[] _testArray; - //[Benchmark(Baseline = true)] + [Benchmark(Baseline = false)] public void Array_Create_WithCapacity() { - DoNothing(new object[TestObjectCount]); + var data = new object[TestObjectCount]; + DoNothing(data); } - //[Benchmark] - public void Array_GetItem() - { - var data = _testArray; - var dataCount = TestObjectCount; - for (var i = 0; i < dataCount; i++) - { - DoNothing(data[i]); - } - } - - //[Benchmark] + [Benchmark(Baseline = true)] public void Array_SetItem() { - var data = _testArray; + var data = TestObjects; var dataCount = TestObjectCount; for (var i = 0; i < dataCount; i++) { @@ -35,14 +25,25 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark] + [Benchmark] + public void Array_GetItem() + { + var data = TestObjects; + var dataCount = TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(data[i]); + } + } + + [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 af73248..766a3b3 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.List.cs @@ -6,7 +6,7 @@ namespace Recyclable.Collections.Benchmarks { private readonly List _testList; - //[Benchmark(Baseline = true)] + //[Benchmark(Baseline = false)] public void List_Create() { var list = new List(); @@ -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 = false)] public void List_Add_WithCapacity() { var data = TestObjects; @@ -52,14 +52,38 @@ namespace Recyclable.Collections.Benchmarks } [Benchmark] - public void List_AddRange() + public void List_AddRangeWhenSourceIsArray() { var data = TestObjects; var list = new List(); list.AddRange(data); } - [Benchmark(Baseline = true)] + [Benchmark] + public void List_AddRangeWhenSourceIsList() + { + var data = TestObjectsAsList; + var list = new List(); + list.AddRange(data); + } + + [Benchmark] + public void List_AddRangeWhenSourceIsIList() + { + var data = (IList)TestObjectsAsList; + var list = new List(); + list.AddRange(data); + } + + [Benchmark] + public void List_AddRangeWhenSourceIsIEnumerable() + { + var data = (IEnumerable)TestObjects; + var list = new List(); + list.AddRange(data); + } + + [Benchmark(Baseline = false)] public void List_AddRange_WithCapacity() { var data = TestObjects; @@ -68,21 +92,10 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - //[Benchmark] - public void List_GetItem() - { - var data = _testList; - var dataCount = TestObjectCount; - for (var i = 0; i < dataCount; i++) - { - DoNothing(data[i]); - } - } - - //[Benchmark] + [Benchmark] public void List_SetItem() { - var data = _testList; + var data = TestObjectsAsList; var dataCount = TestObjectCount; for (var i = 0; i < dataCount; i++) { @@ -90,7 +103,18 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark] + [Benchmark] + public void List_GetItem() + { + var data = TestObjectsAsList; + var dataCount = TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(data[i]); + } + } + + [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 5451f76..08d3f16 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; @@ -66,13 +66,45 @@ namespace Recyclable.Collections.Benchmarks } [Benchmark] - public void RecyclableArrayList_AddRange() + public void RecyclableArrayList_AddRangeWhenSourceIsArray() { var data = TestObjects; using var list = new RecyclableArrayList(); list.AddRange(data); } + [Benchmark] + public void RecyclableArrayList_AddRangeWhenSourceIsList() + { + var data = TestObjectsAsList; + using var list = new RecyclableArrayList(); + list.AddRange(data); + } + + [Benchmark] + public void RecyclableArrayList_AddRangeWhenSourceIsIList() + { + var data = (IList)TestObjectsAsList; + using var list = new RecyclableArrayList(); + list.AddRange(data); + } + + [Benchmark] + public void RecyclableArrayList_AddRangeWhenSourceIsIEnumerable() + { + var data = (IEnumerable)TestObjects; + using var list = new RecyclableArrayList(); + list.AddRange(data); + } + + [Benchmark] + public void RecyclableArrayList_AddRangeWhenSourceIsRecyclableArrayList() + { + var data = TestObjectsAsRecyclableArrayList; + using var list = new RecyclableArrayList(); + list.AddRange(data); + } + [Benchmark] public void RecyclableArrayList_AddRange_WithCapacity() { @@ -82,10 +114,23 @@ namespace Recyclable.Collections.Benchmarks list.AddRange(data); } - //[Benchmark] + [Benchmark] + public void RecyclableArrayList_SetItem() + { + var data = TestObjectsAsRecyclableArrayList; + var dataCount = TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + data[i] = TestObjects[i]; + } + + data.Count = dataCount; + } + + [Benchmark] public void RecyclableArrayList_GetItem() { - var data = _testRecyclableArrayList; + var data = TestObjectsAsRecyclableArrayList; var dataCount = TestObjectCount; for (var i = 0; i < dataCount; i++) { @@ -93,18 +138,7 @@ namespace Recyclable.Collections.Benchmarks } } - //[Benchmark] - public void RecyclableArrayList_SetItem() - { - var data = _testRecyclableArrayList; - var dataCount = TestObjectCount; - for (var i = 0; i < dataCount; i++) - { - data[i] = TestObjects[i]; - } - } - - //[Benchmark] + [Benchmark] public void RecyclableArrayList_Count() { var data = _testRecyclableArrayList; diff --git a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs index 27ff894..d6a166c 100644 --- a/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs +++ b/Recyclable.Collections.Benchmarks/RecyclableCollectionsBenchmarks.RecyclableList.cs @@ -27,7 +27,7 @@ namespace Recyclable.Collections.Benchmarks 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.Benchmarks/RefVsInstanceMemberBenchmarks.cs b/Recyclable.Collections.Benchmarks/RefVsInstanceMemberBenchmarks.cs index ed3c1b3..2851a58 100644 --- a/Recyclable.Collections.Benchmarks/RefVsInstanceMemberBenchmarks.cs +++ b/Recyclable.Collections.Benchmarks/RefVsInstanceMemberBenchmarks.cs @@ -18,14 +18,27 @@ namespace Recyclable.Collections.Benchmarks private class TestCase { + private int[] _items = new int[123]; private int _count = 123; + private int Add(int count) + { + return count; + } + public int Count { get => _count; set => _count = value; } - public ref int GetRefCount() + public ref int GetCountAsRefCount2() + { + ref int count = ref _count; + count = 234; + return ref count; + } + + public ref int GetCountAsRefCount3() { ref int count = ref _count; count = 234; @@ -33,30 +46,299 @@ namespace Recyclable.Collections.Benchmarks return ref count; } - private int Add(int count) + public ref int GetCountAsRefCount4() { - return count; + ref int count = ref _count; + count = 234; + _ = Add(count); + _ = Add(count); + return ref count; } - public int GetCount() + public ref int GetCountAsRefCount5() + { + ref int count = ref _count; + count = 234; + _ = Add(count); + _ = Add(count); + _ = Add(count); + return ref count; + } + + public int GetCountAsInstanceMember2() + { + _count = 234; + return _count; + } + + public int GetCountAsInstanceMember3() { _count = 234; _ = Add(_count); return _count; } + + public int GetCountAsInstanceMember4() + { + _count = 234; + _ = Add(_count); + _ = Add(_count); + return _count; + } + + public int GetCountAsInstanceMember5() + { + _count = 234; + _ = Add(_count); + _ = Add(_count); + _ = Add(_count); + return _count; + } + + public int GetLengthAsArray2() + { + _ = Add(_items.Length); + return _items.Length; + } + + public int GetLengthAsArray3() + { + _ = Add(_items.Length); + _ = Add(_items.Length); + return _items.Length; + } + + public int GetLengthAsArray4() + { + _ = Add(_items.Length); + _ = Add(_items.Length); + _ = Add(_items.Length); + return _items.Length; + } + + public int GetLengthAsArray5() + { + _ = Add(_items.Length); + _ = Add(_items.Length); + _ = Add(_items.Length); + _ = Add(_items.Length); + return _items.Length; + } + + public int GetLengthAsArray6() + { + _ = Add(_items.Length); + _ = Add(_items.Length); + _ = Add(_items.Length); + _ = Add(_items.Length); + _ = Add(_items.Length); + return _items.Length; + } + + public int GetLengthAsArrayLocalVar5() + { + var items = _items; + _ = Add(items.Length); + _ = Add(items.Length); + _ = Add(items.Length); + _ = Add(items.Length); + return items.Length; + } + + public int GetLengthAsArrayLocalVar6() + { + var items = _items; + _ = Add(items.Length); + _ = Add(items.Length); + _ = Add(items.Length); + _ = Add(items.Length); + _ = Add(items.Length); + return items.Length; + } + + public int GetLengthAsRefArray2() + { + ref int[] items = ref _items; + _ = Add(items.Length); + return items.Length; + } + + public int GetLengthAsRefArray3() + { + ref int[] items = ref _items; + _ = Add(items.Length); + _ = Add(items.Length); + return items.Length; + } + + public int GetLengthAsRefArray4() + { + ref int[] items = ref _items; + _ = Add(items.Length); + _ = Add(items.Length); + _ = Add(items.Length); + return items.Length; + } + + public int GetLengthAsRefArray5() + { + ref int[] items = ref _items; + _ = Add(items.Length); + _ = Add(items.Length); + _ = Add(items.Length); + _ = Add(items.Length); + return items.Length; + } + + public int GetLengthAsRefArray6() + { + ref int[] items = ref _items; + _ = Add(items.Length); + _ = Add(items.Length); + _ = Add(items.Length); + _ = Add(items.Length); + _ = Add(items.Length); + return items.Length; + } } [Benchmark] - public void Ref() + public void RefToInt2() { - var count = _testCase.GetRefCount(); + var count = _testCase.GetCountAsRefCount2(); + DoNothing(count); + } + + [Benchmark] + public void RefToInt3() + { + var count = _testCase.GetCountAsRefCount3(); + DoNothing(count); + } + + [Benchmark] + public void RefToInt4() + { + var count = _testCase.GetCountAsRefCount4(); + DoNothing(count); + } + + [Benchmark] + public void RefToInt5() + { + var count = _testCase.GetCountAsRefCount5(); DoNothing(count); } [Benchmark(Baseline = true)] - public void InstanceMember() + public void IntInstanceMember2() { - var count = _testCase.GetCount(); + var count = _testCase.GetCountAsInstanceMember2(); + DoNothing(count); + } + + [Benchmark(Baseline = false)] + public void IntInstanceMember3() + { + var count = _testCase.GetCountAsInstanceMember3(); + DoNothing(count); + } + + [Benchmark(Baseline = false)] + public void IntInstanceMember4() + { + var count = _testCase.GetCountAsInstanceMember4(); + DoNothing(count); + } + + [Benchmark(Baseline = false)] + public void IntInstanceMember5() + { + var count = _testCase.GetCountAsInstanceMember5(); + DoNothing(count); + } + + [Benchmark] + public void RefToArray2() + { + var count = _testCase.GetLengthAsRefArray2(); + DoNothing(count); + } + + [Benchmark] + public void RefToArray3() + { + var count = _testCase.GetLengthAsRefArray3(); + DoNothing(count); + } + + [Benchmark] + public void RefToArray4() + { + var count = _testCase.GetLengthAsRefArray4(); + DoNothing(count); + } + + [Benchmark] + public void RefToArray5() + { + var count = _testCase.GetLengthAsRefArray5(); + DoNothing(count); + } + + [Benchmark] + public void RefToArray6() + { + var count = _testCase.GetLengthAsRefArray6(); + DoNothing(count); + } + + [Benchmark(Baseline = false)] + public void ArrayInstanceMember2() + { + var count = _testCase.GetLengthAsArray2(); + DoNothing(count); + } + + [Benchmark(Baseline = false)] + public void ArrayInstanceMember3() + { + var count = _testCase.GetLengthAsArray3(); + DoNothing(count); + } + + [Benchmark(Baseline = false)] + public void ArrayInstanceMember4() + { + var count = _testCase.GetLengthAsArray4(); + DoNothing(count); + } + + [Benchmark(Baseline = false)] + public void ArrayInstanceMember5() + { + var count = _testCase.GetLengthAsArray5(); + DoNothing(count); + } + + [Benchmark(Baseline = false)] + public void ArrayInstanceMember6() + { + var count = _testCase.GetLengthAsArray6(); + DoNothing(count); + } + + [Benchmark(Baseline = false)] + public void ArrayInstanceMemberLocalVar5() + { + var count = _testCase.GetLengthAsArrayLocalVar5(); + DoNothing(count); + } + + [Benchmark(Baseline = false)] + public void ArrayInstanceMemberLocalVar6() + { + var count = _testCase.GetLengthAsArrayLocalVar6(); DoNothing(count); } } diff --git a/Recyclable.Collections/RecyclableArrayList.cs b/Recyclable.Collections/RecyclableArrayList.cs index 27fa4ea..2542b79 100644 --- a/Recyclable.Collections/RecyclableArrayList.cs +++ b/Recyclable.Collections/RecyclableArrayList.cs @@ -82,7 +82,6 @@ namespace Recyclable.Collections 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); @@ -90,6 +89,34 @@ namespace Recyclable.Collections } } +#pragma warning disable CS8618 // _memory will be initialized when the 1st item is added + public RecyclableArrayList(RecyclableArrayList source) +#pragma warning restore CS8618 + { + AddRange(source); + } + +#pragma warning disable CS8618 // _memory will be initialized when the 1st item is added + public RecyclableArrayList(in T[] source) +#pragma warning restore CS8618 + { + AddRange(source); + } + +#pragma warning disable CS8618 // _memory will be initialized when the 1st item is added + public RecyclableArrayList(List source) +#pragma warning restore CS8618 + { + AddRange(source); + } + +#pragma warning disable CS8618 // _memory will be initialized when the 1st item is added + public RecyclableArrayList(IList source) +#pragma warning restore CS8618 + { + AddRange(source); + } + public RecyclableArrayList(IEnumerable source, int initialCapacity = RecyclableDefaults.Capacity) { _memory = SetNewLength(_memory, initialCapacity); @@ -126,7 +153,7 @@ namespace Recyclable.Collections public bool IsReadOnly { get; } = false; - public void Add(in T item) + public void Add(T item) { int requestedCapacity = _count + 1; if (_capacity < requestedCapacity) @@ -152,7 +179,7 @@ namespace Recyclable.Collections _count = targetCapacity; } - public void AddRange(in List items) + public void AddRange(List items) { var sourceItemsCount = items.Count; var targetCapacity = _count + sourceItemsCount; @@ -165,7 +192,20 @@ namespace Recyclable.Collections _count = targetCapacity; } - public void AddRange(in RecyclableArrayList items) + public void AddRange(IList items) + { + var sourceItemsCount = items.Count; + var targetCapacity = _count + sourceItemsCount; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(targetCapacity); + } + + items.CopyTo(_memory, _count); + _count = targetCapacity; + } + + public void AddRange(RecyclableArrayList items) { var sourceItemsCount = items._count; var targetCapacity = _count + sourceItemsCount; @@ -180,10 +220,17 @@ namespace Recyclable.Collections _count = targetCapacity; } - public void AddRange(IEnumerable source) + public void AddRange(IEnumerable source, int growByCount = 100) { + ref int capacity = ref _capacity; + ref int count = ref _count; foreach (var item in source) { + if (capacity == count) + { + _ = EnsureCapacity(capacity * growByCount); + } + Add(item); } } diff --git a/Recyclable.Collections/RecyclableArrayList_CreateBenchmarks.png b/Recyclable.Collections/RecyclableArrayList_CreateBenchmarks.png index ed3622a..b70bdfd 100644 Binary files a/Recyclable.Collections/RecyclableArrayList_CreateBenchmarks.png and b/Recyclable.Collections/RecyclableArrayList_CreateBenchmarks.png differ diff --git a/Recyclable.Collections/RecyclableDefaults.cs b/Recyclable.Collections/RecyclableDefaults.cs index 09e80ce..b7ea187 100644 --- a/Recyclable.Collections/RecyclableDefaults.cs +++ b/Recyclable.Collections/RecyclableDefaults.cs @@ -3,7 +3,7 @@ public class RecyclableDefaults { public const int BlockSize = 10_240; - public const int Capacity = 8; + public const int Capacity = 1; public const int MinPooledArrayLength = 100; } } diff --git a/Recyclable.Collections/RecyclableListExtensions.cs b/Recyclable.Collections/RecyclableListExtensions.cs index 03ae6dc..959c07c 100644 --- a/Recyclable.Collections/RecyclableListExtensions.cs +++ b/Recyclable.Collections/RecyclableListExtensions.cs @@ -121,11 +121,26 @@ namespace Recyclable.Collections 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 List values, int blockSize = RecyclableDefaults.BlockSize) => new(values, blockSize); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RecyclableArrayList ToRecyclableArrayList(this IEnumerable values) => new(values, values.TryGetNonEnumeratedCount(out int count) ? count : RecyclableDefaults.Capacity); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RecyclableArrayList ToRecyclableArrayList(this T[] values) => new(values); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RecyclableArrayList ToRecyclableArrayList(this List values) => new(values); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RecyclableArrayList ToRecyclableArrayList(this RecyclableArrayList values) => new(values); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RecyclableList ToRecyclableList(this IEnumerable values, int blockSize = RecyclableDefaults.BlockSize) => new(values, blockSize); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static T[] RentArrayFromPool(this int minSize, ArrayPool arrayPool) => (minSize >= _minPooledArraySize) diff --git a/Recyclable.CollectionsTests/RecyclableArrayListTests.cs b/Recyclable.CollectionsTests/RecyclableArrayListTests.cs index 6fcc043..f482d9c 100644 --- a/Recyclable.CollectionsTests/RecyclableArrayListTests.cs +++ b/Recyclable.CollectionsTests/RecyclableArrayListTests.cs @@ -161,7 +161,7 @@ namespace Recyclable.CollectionsTests } _ = list.Should().BeEmpty(); - _ = list.Capacity.Should().Be(32); + _ = list.Capacity.Should().Be(20); } [Fact] @@ -184,7 +184,7 @@ namespace Recyclable.CollectionsTests } _ = list.Should().BeEmpty(); - _ = list.Capacity.Should().Be(32); + _ = list.Capacity.Should().Be(20); } [Fact]