From 22d8593e1fd6beff97b7d16182d3776c28a20723 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 17 Sep 2023 13:12:16 +0200 Subject: [PATCH] Support for custom boundaries implemented. v1 - will be reverted due to 5x slower performance. --- Recyclable.Collections/MemoryBucket.cs | 10 +-- .../Pools/OneSizeArrayPool.cs | 6 +- .../Pools/RecyclableArrayPool.cs | 85 ++++++++++++++++-- .../RecyclableList.AddRange.cs | 32 +++---- .../RecyclableList.Enumerator.cs | 4 +- .../RecyclableList.Helpers.cs | 24 +++-- .../RecyclableList.IList.cs | 6 +- .../RecyclableList.VersionedEnumerator.cs | 4 +- Recyclable.Collections/RecyclableList.cs | 87 +++++++++++-------- .../RecyclableLongList.AddRange.cs | 6 +- .../RecyclableLongList.Helpers.cs | 4 +- .../RecyclableListTests.cs | 16 ++-- 12 files changed, 192 insertions(+), 92 deletions(-) diff --git a/Recyclable.Collections/MemoryBucket.cs b/Recyclable.Collections/MemoryBucket.cs index 54a228a..21552ab 100644 --- a/Recyclable.Collections/MemoryBucket.cs +++ b/Recyclable.Collections/MemoryBucket.cs @@ -2,23 +2,21 @@ namespace Recyclable.Collections { internal sealed class MemoryBucket { - public MemoryBucket(T[] memory, MemoryBucket nextBucket) + public MemoryBucket(Array memory, MemoryBucket nextBucket) { _memory = memory; _nextBucket = nextBucket; } - public MemoryBucket(T[] memory) + public MemoryBucket(Array memory) { _memory = memory; } -#pragma warning disable RCS1085 - private T[] _memory; - public T[] Memory { get => _memory; set => _memory = value; } + private Array _memory; + public Array Memory { get => _memory; set => _memory = value; } private MemoryBucket? _nextBucket; public MemoryBucket? NextBucket { get => _nextBucket; set => _nextBucket = value; } -#pragma warning restore RCS1085 } } diff --git a/Recyclable.Collections/Pools/OneSizeArrayPool.cs b/Recyclable.Collections/Pools/OneSizeArrayPool.cs index 4c50db1..146266b 100644 --- a/Recyclable.Collections/Pools/OneSizeArrayPool.cs +++ b/Recyclable.Collections/Pools/OneSizeArrayPool.cs @@ -39,7 +39,7 @@ _ = Task.Run(ReleaseMemoryOnExcess); } - public T[] Rent() + public Array Rent() { MemoryBucket? bucket; Enter(); @@ -55,10 +55,10 @@ } Exit(); - return bucket != null ? bucket.Memory : new T[_blockSize]; + return bucket != null ? bucket.Memory : Array.CreateInstance(typeof(T), _blockSize); } - public void ReturnUnchecked(T[] array, bool clearArray) + public void ReturnUnchecked(Array array, bool clearArray) { MemoryBucket bucket = new(array); if (clearArray) diff --git a/Recyclable.Collections/Pools/RecyclableArrayPool.cs b/Recyclable.Collections/Pools/RecyclableArrayPool.cs index dfe5c66..e1f9595 100644 --- a/Recyclable.Collections/Pools/RecyclableArrayPool.cs +++ b/Recyclable.Collections/Pools/RecyclableArrayPool.cs @@ -4,24 +4,95 @@ namespace Recyclable.Collections.Pools { public readonly struct RecyclableArrayPool { - internal static readonly OneSizeArrayPool[] _pools = new OneSizeArrayPool[32]; - public static T[] RentShared(int blockSize) => _pools[31 - BitOperations.LeadingZeroCount((uint)blockSize)].Rent(); + private const int MaxArraySize = 32; + internal static readonly OneSizeArrayPool[] _pools = new OneSizeArrayPool[MaxArraySize]; + internal static readonly Dictionary[]> _poolsWithBoundaries = new(); + + public static Array RentShared(int blockSize, int lowerBound) + { + OneSizeArrayPool[]? pools; + lock(_poolsWithBoundaries) + { + if (!_poolsWithBoundaries.TryGetValue(lowerBound, out pools)) + { + pools = _poolsWithBoundaries[lowerBound] = new OneSizeArrayPool[MaxArraySize]; + InitializePools(pools); + } + } + + return pools[31 - BitOperations.LeadingZeroCount((uint)blockSize)].Rent(); + } + + public static Array RentShared(int blockSize) => _pools[31 - BitOperations.LeadingZeroCount((uint)blockSize)].Rent(); + public static T[] RentSharedAsTyped(int blockSize) => (T[])_pools[31 - BitOperations.LeadingZeroCount((uint)blockSize)].Rent(); + public static void ReturnShared(in T[] memoryBlock, bool needsClearing) - => _pools[31 - BitOperations.LeadingZeroCount((uint)memoryBlock.Length)].ReturnUnchecked(memoryBlock, needsClearing); + { + if (memoryBlock.GetLowerBound(0) == 0) + { + _pools[31 - BitOperations.LeadingZeroCount((uint)memoryBlock.Length)].ReturnUnchecked(memoryBlock, needsClearing); + } + else + { + OneSizeArrayPool[] pools; + lock(_poolsWithBoundaries) + { + pools = _poolsWithBoundaries[memoryBlock.GetLowerBound(0)]; + } + + pools[31 - BitOperations.LeadingZeroCount((uint)memoryBlock.Length)].ReturnUnchecked(memoryBlock, needsClearing); + } + } + + public static void ReturnShared(in Array memoryBlock, bool needsClearing) + { + if (memoryBlock.GetLowerBound(0) == 0) + { + _pools[31 - BitOperations.LeadingZeroCount((uint)memoryBlock.Length)].ReturnUnchecked(memoryBlock, needsClearing); + } + else + { + OneSizeArrayPool[] pools; + lock(_poolsWithBoundaries) + { + pools = _poolsWithBoundaries[memoryBlock.GetLowerBound(0)]; + } + + pools[31 - BitOperations.LeadingZeroCount((uint)memoryBlock.Length)].ReturnUnchecked(memoryBlock, needsClearing); + } + } public static ref OneSizeArrayPool Shared(int blockSize) => ref _pools[31 - BitOperations.LeadingZeroCount((uint)blockSize)]; + public static ref OneSizeArrayPool SharedWithBoundaries(int blockSize, int lowerBound) + { + OneSizeArrayPool[]? pools; + lock(_poolsWithBoundaries) + { + if (!_poolsWithBoundaries.TryGetValue(lowerBound, out pools)) + { + pools = _poolsWithBoundaries[lowerBound] = new OneSizeArrayPool[MaxArraySize]; + InitializePools(pools); + } + } + + return ref pools[31 - BitOperations.LeadingZeroCount((uint)blockSize)]; + } + static RecyclableArrayPool() { - var pools = _pools; - for (var arraySize = 0; arraySize < 32; arraySize++) + InitializePools(_pools); + _ = Task.Run(CheckAndReportMemoryPressure); + } + + private static void InitializePools(OneSizeArrayPool[] pools) + { + for (var arraySize = 0; arraySize < MaxArraySize; arraySize++) { int blockSize = 1 << arraySize; pools[arraySize] = new OneSizeArrayPool(blockSize, 1000); } - - _ = Task.Run(CheckAndReportMemoryPressure); } public static readonly OneSizeArrayPool Null = new(0, 0); diff --git a/Recyclable.Collections/RecyclableList.AddRange.cs b/Recyclable.Collections/RecyclableList.AddRange.cs index 2f153c0..7bcf4df 100644 --- a/Recyclable.Collections/RecyclableList.AddRange.cs +++ b/Recyclable.Collections/RecyclableList.AddRange.cs @@ -1,14 +1,10 @@ -using System; using System.Collections; -using System.Collections.Generic; -using System.Linq; using System.Numerics; using System.Runtime.CompilerServices; -using System.Threading.Tasks; namespace Recyclable.Collections { - public static class zRecyclableListAddRange + public static class zRecyclableListAddRange { private static void AddRangeEnumerated(this RecyclableList list, IEnumerable items, int growByCount) { @@ -19,7 +15,7 @@ namespace Recyclable.Collections var enumerator = items.GetEnumerator(); int capacity = list._capacity; - memorySpan = new(list._memoryBlock); + memorySpan = new((T[])list._memoryBlock); if (enumerator.MoveNext()) { int available = capacity - targetItemIndex; @@ -28,7 +24,7 @@ namespace Recyclable.Collections if (targetItemIndex + growByCount > capacity) { capacity = RecyclableListHelpers.ResizeAndCopy(list, targetItemIndex, checked((int)BitOperations.RoundUpToPowerOf2((uint)(targetItemIndex + growByCount)))); - memorySpan = new(list._memoryBlock); + memorySpan = new((T[])list._memoryBlock); available = capacity - targetItemIndex; } @@ -52,7 +48,7 @@ namespace Recyclable.Collections { list._capacity = RecyclableListHelpers.ResizeAndCopy(list, currentItemsCount, checked((int)BitOperations.RoundUpToPowerOf2((uint)(currentItemsCount + requiredAdditionalCapacity)))); - Span memorySpan = new(list._memoryBlock); + Span memorySpan = new((T[])list._memoryBlock); foreach (var item in items) { memorySpan[currentItemsCount++] = item; @@ -82,7 +78,7 @@ namespace Recyclable.Collections list._capacity = RecyclableListHelpers.ResizeAndCopy(list, list._count, checked((int)BitOperations.RoundUpToPowerOf2((uint)(list._count + items.Length)))); } - new Span(items).CopyTo(new Span(list._memoryBlock, list._count, items.Length)); + new Span(items).CopyTo(new Span((T[])list._memoryBlock, list._count, items.Length)); list._count += items.Length; list._version++; } @@ -95,7 +91,7 @@ namespace Recyclable.Collections list._capacity = RecyclableListHelpers.ResizeAndCopy(list, list._count, checked((int)BitOperations.RoundUpToPowerOf2((uint)(list._count + items.Length)))); } - items.CopyTo(new Span(list._memoryBlock, list._count, items.Length)); + items.CopyTo(new Span((T[])list._memoryBlock, list._count, items.Length)); list._count += items.Length; list._version++; } @@ -108,7 +104,7 @@ namespace Recyclable.Collections list._capacity = RecyclableListHelpers.ResizeAndCopy(list, list._count, checked((int)BitOperations.RoundUpToPowerOf2((uint)(list._count + items.Length)))); } - items.CopyTo(new Span(list._memoryBlock, list._count, items.Length)); + items.CopyTo(new Span((T[])list._memoryBlock, list._count, items.Length)); list._count += items.Length; list._version++; } @@ -121,7 +117,7 @@ namespace Recyclable.Collections list._capacity = RecyclableListHelpers.ResizeAndCopy(list, list._count, checked((int)BitOperations.RoundUpToPowerOf2((uint)(list._count + items.Count)))); } - items.CopyTo(list._memoryBlock, list._count); + items.CopyTo((T[])list._memoryBlock, list._count); list._count += items.Count; list._version++; } @@ -147,7 +143,7 @@ namespace Recyclable.Collections list._capacity = RecyclableListHelpers.ResizeAndCopy(list, list._count, checked((int)BitOperations.RoundUpToPowerOf2((uint)(list._count + items.Count)))); } - items.CopyTo(list._memoryBlock, list._count); + items.CopyTo((T[])list._memoryBlock, list._count); list._count += items.Count; list._version++; } @@ -160,7 +156,7 @@ namespace Recyclable.Collections list._capacity = RecyclableListHelpers.ResizeAndCopy(list, list._count, checked((int)BitOperations.RoundUpToPowerOf2((uint)(list._count + items._count)))); } - new Span(items._memoryBlock, 0, items._count).CopyTo(new Span(list._memoryBlock, list._count, items._count)); + new Span((T[])items._memoryBlock, 0, items._count).CopyTo(new Span((T[])list._memoryBlock, list._count, items._count)); list._count += items._count; list._version++; } @@ -187,7 +183,7 @@ namespace Recyclable.Collections } // TODO: Avoid unnecessary range operator - pass as arguments instead. - Span targetSpan = new Span(list._memoryBlock)[oldCount..], + Span targetSpan = new Span((T[])list._memoryBlock)[oldCount..], itemsSpan; int blockIndex, @@ -225,7 +221,7 @@ namespace Recyclable.Collections var enumerator = source.GetEnumerator(); int capacity = list._capacity; - memorySpan = new(list._memoryBlock); + memorySpan = new((T[])list._memoryBlock); if (enumerator.MoveNext()) { int available = capacity - targetItemIndex; @@ -234,7 +230,7 @@ namespace Recyclable.Collections if (targetItemIndex + growByCount > capacity) { capacity = RecyclableListHelpers.ResizeAndCopy(list, targetItemIndex, checked((int)BitOperations.RoundUpToPowerOf2((uint)(targetItemIndex + growByCount)))); - memorySpan = new(list._memoryBlock); + memorySpan = new((T[])list._memoryBlock); available = capacity - targetItemIndex; } @@ -263,7 +259,7 @@ namespace Recyclable.Collections list._capacity = RecyclableListHelpers.ResizeAndCopy(list, list._count, checked((int)BitOperations.RoundUpToPowerOf2((uint)(list._count + sourceItemsCount)))); } - Span memorySpan = new(list._memoryBlock, list._count, sourceItemsCount); + Span memorySpan = new((T[])list._memoryBlock, list._count, sourceItemsCount); for (var sourceItemIndex = 0; sourceItemIndex < sourceItemsCount; sourceItemIndex++) { memorySpan[sourceItemIndex] = items[sourceItemIndex]; diff --git a/Recyclable.Collections/RecyclableList.Enumerator.cs b/Recyclable.Collections/RecyclableList.Enumerator.cs index acaae28..e99237c 100644 --- a/Recyclable.Collections/RecyclableList.Enumerator.cs +++ b/Recyclable.Collections/RecyclableList.Enumerator.cs @@ -16,9 +16,9 @@ namespace Recyclable.Collections #nullable disable [Pure] - public readonly T Current => _list._memoryBlock[_currentItemIndex - 1]; + public readonly T Current => (T)_list._memoryBlock.GetValue(_currentItemIndex - 1); [Pure] - readonly object IEnumerator.Current => _list._memoryBlock[_currentItemIndex - 1]; + readonly object IEnumerator.Current => (T)_list._memoryBlock.GetValue(_currentItemIndex - 1); #nullable restore public bool MoveNext() diff --git a/Recyclable.Collections/RecyclableList.Helpers.cs b/Recyclable.Collections/RecyclableList.Helpers.cs index dd64d43..79b6b5d 100644 --- a/Recyclable.Collections/RecyclableList.Helpers.cs +++ b/Recyclable.Collections/RecyclableList.Helpers.cs @@ -108,13 +108,23 @@ namespace Recyclable.Collections { // TODO: Measure performance // T[] newMemoryBlock = RecyclableListHelpers._arrayPool.Rent(_capacity <<= 1); - T[] oldMemoryBlock = list._memoryBlock; - list._memoryBlock = (list._capacity <<= 1) < RecyclableDefaults.MinPooledArrayLength - ? new T[list._capacity] - : RecyclableArrayPool.RentShared(list._capacity); + Array oldMemoryBlock = list._memoryBlock; + if (oldMemoryBlock.GetLowerBound(0) == 0) + { + list._memoryBlock = (list._capacity <<= 1) < RecyclableDefaults.MinPooledArrayLength + ? Array.CreateInstance(typeof(T), list._capacity) + : RecyclableArrayPool.RentShared(list._capacity); + } + else + { + list._memoryBlock = (list._capacity <<= 1) < RecyclableDefaults.MinPooledArrayLength + ? Array.CreateInstance(typeof(T), new[] { list._capacity }, new[] { oldMemoryBlock.GetLowerBound(0) }) + : RecyclableArrayPool.RentShared(list._capacity, oldMemoryBlock.GetLowerBound(0)); + } // & WAS SLOWER WITHOUT - new Span(oldMemoryBlock, 0, list._count).CopyTo(list._memoryBlock); + Array.Copy(oldMemoryBlock, list._memoryBlock, list._count); + // new Span((T[])oldMemoryBlock, 0, list._count).CopyTo((T[])list._memoryBlock); if (oldMemoryBlock.Length >= RecyclableDefaults.MinPooledArrayLength) { @@ -131,7 +141,7 @@ namespace Recyclable.Collections [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] public static int ResizeAndCopy(RecyclableList sourceList, int oldSize, int newSize) { - T[] oldMemoryBlock = sourceList._memoryBlock; + T[] oldMemoryBlock = (T[])sourceList._memoryBlock; sourceList._memoryBlock = newSize >= RecyclableDefaults.MinPooledArrayLength ? RecyclableArrayPool.RentShared(newSize) : new T[newSize]; @@ -139,7 +149,7 @@ namespace Recyclable.Collections // & WAS SLOWER WITHOUT // & WAS SLOWER AS ARRAY - new Span(oldMemoryBlock, 0, oldSize).CopyTo(sourceList._memoryBlock); + new Span(oldMemoryBlock, 0, oldSize).CopyTo((T[])sourceList._memoryBlock); if (oldMemoryBlock.Length >= RecyclableDefaults.MinPooledArrayLength) { diff --git a/Recyclable.Collections/RecyclableList.IList.cs b/Recyclable.Collections/RecyclableList.IList.cs index a7537b1..e0506fe 100644 --- a/Recyclable.Collections/RecyclableList.IList.cs +++ b/Recyclable.Collections/RecyclableList.IList.cs @@ -27,18 +27,18 @@ namespace Recyclable.Collections object? IList.this[int index] { - get => _memoryBlock[index]; + get => _memoryBlock.GetValue(index); set { if (value is T typedValue) { - new Span(_memoryBlock)[index] = typedValue; + _memoryBlock.SetValue(typedValue, index); } else if (value == null && _defaultIsNull) { #nullable disable - new Span(_memoryBlock)[index] = default; + _memoryBlock.SetValue(default, index); #nullable restore } else diff --git a/Recyclable.Collections/RecyclableList.VersionedEnumerator.cs b/Recyclable.Collections/RecyclableList.VersionedEnumerator.cs index 5e729f7..cfa0001 100644 --- a/Recyclable.Collections/RecyclableList.VersionedEnumerator.cs +++ b/Recyclable.Collections/RecyclableList.VersionedEnumerator.cs @@ -17,10 +17,10 @@ namespace Recyclable.Collections #nullable disable [Pure] - public readonly T Current => _enumeratorVersion == _list._version ? _list._memoryBlock[_currentItemIndex - 1] : throw new InvalidOperationException(); + public readonly T Current => _enumeratorVersion == _list._version ? (T)_list._memoryBlock.GetValue(_currentItemIndex - 1) : throw new InvalidOperationException(); [Pure] - readonly object IEnumerator.Current => _enumeratorVersion == _list._version ? _list._memoryBlock[_currentItemIndex - 1] : throw new InvalidOperationException(); + readonly object IEnumerator.Current => _enumeratorVersion == _list._version ? _list._memoryBlock.GetValue(_currentItemIndex - 1) : throw new InvalidOperationException(); #nullable restore public bool MoveNext() diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index cc90c49..6c29de3 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -10,10 +10,10 @@ namespace Recyclable.Collections { private static readonly bool _needsClearing = !typeof(T).IsValueType; - public static explicit operator ReadOnlySpan(RecyclableList source) => new(source._memoryBlock, 0, source._count); + public static explicit operator ReadOnlySpan(RecyclableList source) => new((T[])source._memoryBlock, 0, source._count); #nullable disable - internal T[] _memoryBlock; + internal Array _memoryBlock; #nullable restore internal ulong _version; @@ -22,85 +22,103 @@ namespace Recyclable.Collections public RecyclableList() { _capacity = RecyclableDefaults.InitialCapacity; - _memoryBlock = new T[RecyclableDefaults.InitialCapacity]; + _memoryBlock = Array.CreateInstance(typeof(T), RecyclableDefaults.InitialCapacity); } - public RecyclableList(int initialCapacity) + public RecyclableList(int initialCapacity = RecyclableDefaults.InitialCapacity, int lowerBound = 0) { - if (initialCapacity >= RecyclableDefaults.InitialCapacity) + if (lowerBound == 0) { - _memoryBlock = initialCapacity >= RecyclableDefaults.MinPooledArrayLength - ? RecyclableArrayPool.RentShared(checked((int)BitOperations.RoundUpToPowerOf2((uint)initialCapacity))) - : new T[initialCapacity]; - _capacity = _memoryBlock.Length; + if (initialCapacity >= RecyclableDefaults.InitialCapacity) + { + _memoryBlock = initialCapacity >= RecyclableDefaults.MinPooledArrayLength + ? RecyclableArrayPool.RentShared(checked((int)BitOperations.RoundUpToPowerOf2((uint)initialCapacity))) + : Array.CreateInstance(typeof(T), initialCapacity); + _capacity = _memoryBlock.Length; + } + else + { + _memoryBlock = Array.CreateInstance(typeof(T), RecyclableDefaults.InitialCapacity); + _capacity = RecyclableDefaults.InitialCapacity; + } } else { - _memoryBlock = new T[RecyclableDefaults.InitialCapacity]; - _capacity = RecyclableDefaults.InitialCapacity; + if (initialCapacity >= RecyclableDefaults.InitialCapacity) + { + _memoryBlock = initialCapacity >= RecyclableDefaults.MinPooledArrayLength + ? RecyclableArrayPool.RentShared(checked((int)BitOperations.RoundUpToPowerOf2((uint)initialCapacity)), lowerBound) + : Array.CreateInstance(typeof(T), new[] { initialCapacity }, new[] { lowerBound }); + _capacity = _memoryBlock.Length; + } + else + { + _memoryBlock = Array.CreateInstance(typeof(T), new[] { RecyclableDefaults.InitialCapacity }, new[] { lowerBound }); + _capacity = RecyclableDefaults.InitialCapacity; + } } } public RecyclableList(in Array source) { _capacity = RecyclableDefaults.InitialCapacity; - _memoryBlock = new T[RecyclableDefaults.InitialCapacity]; + _memoryBlock = Array.CreateInstance(typeof(T), RecyclableDefaults.InitialCapacity); zRecyclableListAddRange.AddRange(this, source); } public RecyclableList(RecyclableList source) { _capacity = RecyclableDefaults.InitialCapacity; - _memoryBlock = new T[RecyclableDefaults.InitialCapacity]; + _memoryBlock = Array.CreateInstance(typeof(T), RecyclableDefaults.InitialCapacity); zRecyclableListAddRange.AddRange(this, source); } public RecyclableList(RecyclableLongList source) { _capacity = RecyclableDefaults.InitialCapacity; - _memoryBlock = new T[RecyclableDefaults.InitialCapacity]; + _memoryBlock = Array.CreateInstance(typeof(T), RecyclableDefaults.InitialCapacity); zRecyclableListAddRange.AddRange(this, source); } public RecyclableList(ReadOnlySpan source) { _capacity = RecyclableDefaults.InitialCapacity; - _memoryBlock = new T[RecyclableDefaults.InitialCapacity]; + _memoryBlock = Array.CreateInstance(typeof(T), RecyclableDefaults.InitialCapacity); zRecyclableListAddRange.AddRange(this, source); } public RecyclableList(in T[] source) { _capacity = RecyclableDefaults.InitialCapacity; - _memoryBlock = new T[RecyclableDefaults.InitialCapacity]; + _memoryBlock = Array.CreateInstance(typeof(T), RecyclableDefaults.InitialCapacity); zRecyclableListAddRange.AddRange(this, source); } public RecyclableList(IReadOnlyList source) { _capacity = RecyclableDefaults.InitialCapacity; - _memoryBlock = new T[RecyclableDefaults.InitialCapacity]; + _memoryBlock = Array.CreateInstance(typeof(T), RecyclableDefaults.InitialCapacity); zRecyclableListAddRange.AddRange(this, source); } public RecyclableList(List source) { _capacity = RecyclableDefaults.InitialCapacity; - _memoryBlock = new T[RecyclableDefaults.InitialCapacity]; + _memoryBlock = Array.CreateInstance(typeof(T), RecyclableDefaults.InitialCapacity); zRecyclableListAddRange.AddRange(this, source); } public RecyclableList(ICollection source) { _capacity = RecyclableDefaults.InitialCapacity; - _memoryBlock = new T[RecyclableDefaults.InitialCapacity]; + _memoryBlock = Array.CreateInstance(typeof(T), RecyclableDefaults.InitialCapacity); zRecyclableListAddRange.AddRange(this, source); } public RecyclableList(ICollection source) { _capacity = RecyclableDefaults.InitialCapacity; - _memoryBlock = new T[RecyclableDefaults.InitialCapacity]; + _memoryBlock = Array.CreateInstance(typeof(T), RecyclableDefaults.InitialCapacity); zRecyclableListAddRange.AddRange(this, source); } @@ -110,13 +128,13 @@ namespace Recyclable.Collections { _memoryBlock = initialCapacity >= RecyclableDefaults.MinPooledArrayLength ? RecyclableArrayPool.RentShared(checked((int)BitOperations.RoundUpToPowerOf2((uint)initialCapacity))) - : new T[initialCapacity]; + : Array.CreateInstance(typeof(T), initialCapacity); _capacity = _memoryBlock.Length; } else { _capacity = RecyclableDefaults.InitialCapacity; - _memoryBlock = new T[RecyclableDefaults.InitialCapacity]; + _memoryBlock = Array.CreateInstance(typeof(T), RecyclableDefaults.InitialCapacity); } zRecyclableListAddRange.AddRange(this, source, RecyclableDefaults.MinPooledArrayLength); @@ -128,13 +146,13 @@ namespace Recyclable.Collections { _memoryBlock = initialCapacity >= RecyclableDefaults.MinPooledArrayLength ? RecyclableArrayPool.RentShared(checked((int)BitOperations.RoundUpToPowerOf2((uint)initialCapacity))) - : new T[initialCapacity]; + : Array.CreateInstance(typeof(T), initialCapacity); _capacity = _memoryBlock.Length; } else { _capacity = RecyclableDefaults.InitialCapacity; - _memoryBlock = new T[RecyclableDefaults.InitialCapacity]; + _memoryBlock = Array.CreateInstance(typeof(T), RecyclableDefaults.InitialCapacity); } zRecyclableListAddRange.AddRange(this, source); @@ -143,21 +161,23 @@ namespace Recyclable.Collections public RecyclableList(Span source) { _capacity = RecyclableDefaults.InitialCapacity; - _memoryBlock = new T[RecyclableDefaults.InitialCapacity]; + _memoryBlock = Array.CreateInstance(typeof(T), RecyclableDefaults.InitialCapacity); zRecyclableListAddRange.AddRange(this, source); } public T this[int index] { +#nullable disable [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _memoryBlock[index]; + get => (T)_memoryBlock.GetValue(index); [MethodImpl(MethodImplOptions.AggressiveInlining)] set { - _memoryBlock[index] = value; + _memoryBlock.SetValue(value, index); _version++; } +#nullable restore } internal int _capacity; @@ -187,11 +207,11 @@ namespace Recyclable.Collections RecyclableListHelpers.ResizeAndCopy(this); } - _memoryBlock[_count++] = item; + _memoryBlock.SetValue(item, _count++); _version++; } - public T[] AsArray => _memoryBlock; + public Array AsArray => _memoryBlock; [MethodImpl(MethodImplOptions.NoInlining)] public void Clear() @@ -230,11 +250,10 @@ namespace Recyclable.Collections if (index < oldCount) { - new Span(_memoryBlock, index, oldCount -= index) - .CopyTo(new Span(_memoryBlock, index + 1, oldCount)); + Array.Copy(_memoryBlock, index, _memoryBlock, index + 1, oldCount -= index); } - _memoryBlock[index] = item; + _memoryBlock.SetValue(item, index); _count++; _version++; } @@ -253,7 +272,7 @@ namespace Recyclable.Collections if (_needsClearing) { - _memoryBlock[_count] = default; + _memoryBlock.SetValue(default, _count); } _version++; @@ -280,7 +299,7 @@ namespace Recyclable.Collections if (_needsClearing) { - _memoryBlock[_count] = default; + _memoryBlock.SetValue(default, _count); } _version++; diff --git a/Recyclable.Collections/RecyclableLongList.AddRange.cs b/Recyclable.Collections/RecyclableLongList.AddRange.cs index dc96591..658b685 100644 --- a/Recyclable.Collections/RecyclableLongList.AddRange.cs +++ b/Recyclable.Collections/RecyclableLongList.AddRange.cs @@ -189,7 +189,7 @@ namespace Recyclable.Collections // We're better off to temporarily copy it to a fixed array, // than copying them item by item. We may run into OOMs, though. - T[] itemsBuffer = items.Count >= RecyclableDefaults.MinPooledArrayLength ? RecyclableArrayPool.RentShared((int)BitOperations.RoundUpToPowerOf2((uint)items.Count)) : new T[items.Count]; + T[] itemsBuffer = items.Count >= RecyclableDefaults.MinPooledArrayLength ? (T[])RecyclableArrayPool.RentShared((int)BitOperations.RoundUpToPowerOf2((uint)items.Count)) : new T[items.Count]; items.CopyTo(itemsBuffer, 0); Span itemsSpan = new(itemsBuffer, 0, items.Count); @@ -255,7 +255,7 @@ namespace Recyclable.Collections // We're better off to temporarily copy it to a fixed array, // than copying them item by item. We may run into OOMs, though. - T[] itemsBuffer = items.Count >= RecyclableDefaults.MinPooledArrayLength ? RecyclableArrayPool.RentShared((int)BitOperations.RoundUpToPowerOf2((uint)items.Count)) : new T[items.Count]; + T[] itemsBuffer = items.Count >= RecyclableDefaults.MinPooledArrayLength ? (T[])RecyclableArrayPool.RentShared((int)BitOperations.RoundUpToPowerOf2((uint)items.Count)) : new T[items.Count]; items.CopyTo(itemsBuffer, 0); @@ -590,7 +590,7 @@ namespace Recyclable.Collections targetList._capacity = RecyclableLongList.Helpers.Resize(targetList, targetList._blockSize, targetList._blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)targetCapacity))); } - Span itemsSpan = new(items._memoryBlock, 0, items._count); + Span itemsSpan = new((T[])items._memoryBlock, 0, items._count); Span targetMemoryBlocksSpan = new(targetList._memoryBlocks); Span targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex], targetList._nextItemIndex, targetList._blockSize - targetList._nextItemIndex); diff --git a/Recyclable.Collections/RecyclableLongList.Helpers.cs b/Recyclable.Collections/RecyclableLongList.Helpers.cs index e2c43cf..2187e61 100644 --- a/Recyclable.Collections/RecyclableLongList.Helpers.cs +++ b/Recyclable.Collections/RecyclableLongList.Helpers.cs @@ -150,7 +150,7 @@ namespace Recyclable.Collections if (requiredBlockCount > sourceBlockCount) { // Allocate new memory block for all arrays - newMemoryBlocks = requiredBlockCount >= RecyclableDefaults.MinPooledArrayLength ? RecyclableArrayPool.RentShared(requiredBlockCount) : new T[requiredBlockCount][]; + newMemoryBlocks = requiredBlockCount >= RecyclableDefaults.MinPooledArrayLength ? (T[][])RecyclableArrayPool.RentShared(requiredBlockCount) : new T[requiredBlockCount][]; // Copy arrays from the old memory block for all arrays if (sourceBlockCount > 0) @@ -179,7 +179,7 @@ namespace Recyclable.Collections ref OneSizeArrayPool blockArrayPool = ref RecyclableArrayPool.Shared(minBlockSize); while (blockIndex >= 0 && newMemoryBlocks[blockIndex] == null) { - newMemoryBlocks[blockIndex--] = blockArrayPool.Rent(); + newMemoryBlocks[blockIndex--] = (T[])blockArrayPool.Rent(); } } else diff --git a/Recyclable.CollectionsTests/RecyclableListTests.cs b/Recyclable.CollectionsTests/RecyclableListTests.cs index c75652c..14f0008 100644 --- a/Recyclable.CollectionsTests/RecyclableListTests.cs +++ b/Recyclable.CollectionsTests/RecyclableListTests.cs @@ -338,13 +338,13 @@ namespace Recyclable.CollectionsTests // Arrange using var list = new RecyclableList(testData, itemsCount); - _ = list.AsArray[..itemsCount].Should().AllSatisfy(x => x.Should().NotBeNull()); + _ = ((object[])list.AsArray)[..itemsCount].Should().AllSatisfy(x => x.Should().NotBeNull()); // Act list.Clear(); // Assert - _ = list.AsArray[..itemsCount].Should().AllSatisfy(x => x.Should().BeNull()); + _ = ((object[])list.AsArray)[..itemsCount].Should().AllSatisfy(x => x.Should().BeNull()); } [Theory] @@ -393,6 +393,12 @@ namespace Recyclable.CollectionsTests _ = list.Should().Equal(testData); } + [Fact] + public void ConstructorShouldCreateArrayWithCustomBoundaries() + { + using var list = new RecyclableList(-10); + } + [Theory] [MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))] public void ConstructorSourceShouldInitializeList(string testCase, IEnumerable testData, int itemsCount) @@ -475,14 +481,14 @@ namespace Recyclable.CollectionsTests // Arrange using var list = new RecyclableList(testData, itemsCount); var listArray = list.AsArray; - _ = list.AsArray[..itemsCount].Should().NotBeNull(); - _ = listArray[..itemsCount].Should().AllSatisfy(x => x.Should().NotBeNull()); + _ = ((object[])list.AsArray)[..itemsCount].Should().NotBeNull(); + _ = ((object[])listArray)[..itemsCount].Should().AllSatisfy(x => x.Should().NotBeNull()); // Act list.Dispose(); // Assert - _ = listArray[..itemsCount].Should().AllSatisfy(x => x.Should().BeNull()); + _ = ((object[])listArray)[..itemsCount].Should().AllSatisfy(x => x.Should().BeNull()); } [Fact]