RecyclableList.Resize performance improvements. We'll always initiate the list to 4 elements to eliminate branching along the way. Before cleanup.

This commit is contained in:
mlemanczyk committed 2023-07-01 13:02:43 +02:00
1 parent 11fd6e3dd0
commit b9181eee23
3 files changed
+221 -50

No files matched your search

+114 -36
View File
@@ -52,29 +52,49 @@ namespace Recyclable.Collections
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static int EnsureCapacity(RecyclableList<T> list, int requestedCapacity)
{
int newCapacity;
switch (list._capacity > 0)
// int newCapacity;
// switch (list._capacity > 0)
// {
// case true:
int newCapacity = list._capacity;
while (newCapacity < requestedCapacity)
{
case true:
newCapacity = list._capacity;
while (newCapacity < requestedCapacity)
{
newCapacity <<= 1;
}
break;
case false:
newCapacity = requestedCapacity;
break;
newCapacity <<= 1;
}
list._memoryBlock = Resize(list._memoryBlock, newCapacity);
newCapacity = list._memoryBlock.Length;
list._capacity = newCapacity;
return newCapacity;
ResizeAndCopy(list, newCapacity);
// break;
// case false:
// newCapacity = requestedCapacity;
// list._memoryBlock = Resize(list, newCapacity);
// break;
// }
// newCapacity = list._memoryBlock.Length;
// list._capacity = newCapacity;
return list._capacity;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static void EnsureExactCapacity(RecyclableList<T> list, int requestedCapacity)
{
// int newCapacity;
// switch (list._capacity > 0)
// {
// case true:
ResizeAndCopy(list, requestedCapacity);
// break;
// case false:
// newCapacity = requestedCapacity;
// list._memoryBlock = Resize(list, newCapacity);
// break;
// }
// var newCapacity = list._memoryBlock.Length;
// list._capacity = list._memoryBlock.Length;
}
[Obsolete("This method WILL be removed in the near future. Please use the existing enumerators instead of it.")]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -237,31 +257,89 @@ namespace Recyclable.Collections
return values[middle];
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static T[] Resize(in T[]? source, int newSize)
// [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
// public static T[] Resize(int newSize)
// // ArrayPool<T> arrayPool = _arrayPool;
// => newSize >= RecyclableDefaults.MinPooledArrayLength
// ? _arrayPool.Rent(newSize)
// : new T[newSize];
internal static readonly ArrayPool<T> _arrayPool = ArrayPool<T>.Create();
private static readonly bool NeedsClearing = !typeof(T).IsValueType;
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
public static void ResizeAndCopy(RecyclableList<T> sourceList, int newSize)
{
ArrayPool<T> arrayPool = _arrayPool;
// ArrayPool<T> arrayPool = _arrayPool;
T[] newMemoryBlock = newSize >= RecyclableDefaults.MinPooledArrayLength
? arrayPool.Rent(newSize)
? _arrayPool.Rent(newSize)
: new T[newSize];
if (source?.Length > 0)
{
Array.Copy(source!, 0, newMemoryBlock, 0, source!.Length);
if (source!.Length >= RecyclableDefaults.MinPooledArrayLength)
{
// TODO: Measure gain vs relying on arrayPool to clear
//if (NeedsClearing)
//{
// Array.Clear(source);
//}
// & WAS SLOWER WITHOUT
T[] oldMemoryBlock = sourceList._memoryBlock;
// If anything, it has been already cleared above, so we don't need to repeat it.
arrayPool.Return(source, NeedsClearing);
}
// & WAS SLOWER AS ARRAY
// new Span<T>(oldMemoryBlock).CopyTo(newMemoryBlock);
Array.Copy(oldMemoryBlock, newMemoryBlock, oldMemoryBlock.Length);
if (oldMemoryBlock.Length >= RecyclableDefaults.MinPooledArrayLength)
{
// TODO: Measure gain vs relying on arrayPool to clear
//if (NeedsClearing)
//{
// Array.Clear(source);
//}
// If anything, it has been already cleared above, so we don't need to repeat it.
_arrayPool.Return(oldMemoryBlock, NeedsClearing);
//_arrayPool.Return(oldMemoryBlock, NeedsClearing);
}
sourceList._memoryBlock = newMemoryBlock;
sourceList._capacity = newMemoryBlock.Length;
}
return newMemoryBlock;
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
public static void ResizeAndCopy(RecyclableList<T> sourceList)
{
// ArrayPool<T> arrayPool = _arrayPool;
// int newSize = _capacity << 1;
if ((sourceList._capacity <<= 1) < RecyclableDefaults.MinPooledArrayLength)
{
Array.Resize(ref sourceList._memoryBlock, sourceList._capacity);
// _capacity <<= 1;
return;
}
T[] newMemoryBlock = _arrayPool.Rent(sourceList._capacity);
//T[] newMemoryBlock = _arrayPool.Rent(sourceList._capacity);
// T[] newMemoryBlock = newSize >= RecyclableDefaults.MinPooledArrayLength
// ? RecyclableListHelpers<T>._arrayPool.Rent(newSize)
// : new T[newSize];
// & WAS SLOWER WITHOUT
T[] oldMemoryBlock = sourceList._memoryBlock;
// & WAS SLOWER AS ARRAY
new Span<T>(oldMemoryBlock).CopyTo(newMemoryBlock);
// Array.Copy(oldMemoryBlock, newMemoryBlock, oldMemoryBlock.Length);
if (oldMemoryBlock.Length >= RecyclableDefaults.MinPooledArrayLength)
{
// TODO: Measure gain vs relying on arrayPool to clear
//if (NeedsClearing)
//{
// Array.Clear(source);
//}
// If anything, it has been already cleared above, so we don't need to repeat it.
_arrayPool.Return(oldMemoryBlock, NeedsClearing);
//_arrayPool.Return(oldMemoryBlock, NeedsClearing);
}
sourceList._memoryBlock = newMemoryBlock;
sourceList._capacity = newMemoryBlock.Length;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
+106 -13
View File
@@ -1,5 +1,4 @@
using System.Buffers;
using System.Collections;
using System.Collections;
using System.Runtime.CompilerServices;
namespace Recyclable.Collections
@@ -7,7 +6,7 @@ namespace Recyclable.Collections
[Serializable]
public sealed partial class RecyclableList<T> : IList<T>, IReadOnlyList<T>, IDisposable
{
private static readonly ArrayPool<T> _arrayPool = ArrayPool<T>.Create();
// private static readonly T[] _emptyMemoryBlock = new T[0];
private static readonly bool NeedsClearing = !typeof(T).IsValueType;
public static explicit operator ReadOnlySpan<T>(RecyclableList<T> source) => new(source._memoryBlock, 0, source.Count);
@@ -74,23 +73,34 @@ namespace Recyclable.Collections
public RecyclableList()
#pragma warning restore CS8618
{
_capacity = 4;
_memoryBlock = new T[4];
}
#pragma warning disable CS8618 // _memory will be initialized when the 1st item is added
public RecyclableList(int initialCapacity)
#pragma warning restore CS8618
{
if (initialCapacity > 0)
if (initialCapacity >= 4)
{
_memoryBlock = RecyclableListHelpers<T>.Resize(_memoryBlock, initialCapacity);
_memoryBlock = initialCapacity >= RecyclableDefaults.MinPooledArrayLength
? RecyclableListHelpers<T>._arrayPool.Rent(initialCapacity)
: new T[initialCapacity];
_capacity = _memoryBlock.Length;
}
else
{
_memoryBlock = new T[4];
_capacity = 4;
}
}
#pragma warning disable CS8618 // _memory will be initialized when the 1st item is added
public RecyclableList(RecyclableList<T> source)
#pragma warning restore CS8618
{
_capacity = 4;
_memoryBlock = new T[4];
AddRange(source);
}
@@ -98,6 +108,8 @@ namespace Recyclable.Collections
public RecyclableList(RecyclableLongList<T> source)
#pragma warning restore CS8618
{
_capacity = 4;
_memoryBlock = new T[4];
AddRange(source);
}
@@ -105,6 +117,8 @@ namespace Recyclable.Collections
public RecyclableList(ReadOnlySpan<T> source)
#pragma warning restore CS8618
{
_capacity = 4;
_memoryBlock = new T[4];
AddRange(source);
}
@@ -112,6 +126,8 @@ namespace Recyclable.Collections
public RecyclableList(in T[] source)
#pragma warning restore CS8618
{
_capacity = 4;
_memoryBlock = new T[4];
AddRange(source);
}
@@ -119,6 +135,8 @@ namespace Recyclable.Collections
public RecyclableList(List<T> source)
#pragma warning restore CS8618
{
_capacity = 4;
_memoryBlock = new T[4];
AddRange(source);
}
@@ -126,13 +144,26 @@ namespace Recyclable.Collections
public RecyclableList(IList<T> source)
#pragma warning restore CS8618
{
_capacity = 4;
_memoryBlock = new T[4];
AddRange(source);
}
public RecyclableList(IEnumerable<T> source, int initialCapacity = RecyclableDefaults.Capacity)
{
_memoryBlock = RecyclableListHelpers<T>.Resize(_memoryBlock, initialCapacity);
_capacity = _memoryBlock.Length;
if (initialCapacity >= 4)
{
_memoryBlock = initialCapacity >= RecyclableDefaults.MinPooledArrayLength
? RecyclableListHelpers<T>._arrayPool.Rent(initialCapacity)
: new T[initialCapacity];
_capacity = _memoryBlock.Length;
}
else
{
_capacity = 4;
_memoryBlock = new T[4];
}
AddRange(source);
}
@@ -157,8 +188,8 @@ namespace Recyclable.Collections
{
if (_capacity != value)
{
_memoryBlock = RecyclableListHelpers<T>.Resize(_memoryBlock, value);
_capacity = _memoryBlock.Length;
RecyclableListHelpers<T>.ResizeAndCopy(this, value);
// _capacity = _memoryBlock.Length;
_version++;
}
}
@@ -169,15 +200,76 @@ namespace Recyclable.Collections
public bool IsReadOnly => false;
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
private void ResizeAndCopy()
{
// TODO: Measure performance
// ArrayPool<T> arrayPool = _arrayPool;
// & WAS SLOWER
// int newSize = _capacity << 1;
// & WAS SLOWER
// if ((_capacity <<= 1) < RecyclableDefaults.MinPooledArrayLength)
// {
// Array.Resize(ref _memoryBlock, _capacity);
// // _capacity <<= 1;
// return;
// }
// TODO: Measure performance after RecyclableArrayPool is reworked
// T[] newMemoryBlock = RecyclableListHelpers<T>._arrayPool.Rent(_capacity <<= 1);
T[] newMemoryBlock = (_capacity <<= 1) >= RecyclableDefaults.MinPooledArrayLength
? RecyclableListHelpers<T>._arrayPool.Rent(_capacity)
: new T[_capacity];
// & WAS SLOWER WITHOUT
T[] oldMemoryBlock = _memoryBlock;
new Span<T>(oldMemoryBlock).CopyTo(newMemoryBlock);
// & WAS SLOWER
// Array.Copy(oldMemoryBlock, newMemoryBlock, oldMemoryBlock.Length);
if (oldMemoryBlock.Length >= RecyclableDefaults.MinPooledArrayLength)
{
// TODO: Measure gain vs relying on arrayPool to clear
//if (NeedsClearing)
//{
// Array.Clear(source);
//}
// // If anything, it has been already cleared above, so we don't need to repeat it.
RecyclableListHelpers<T>._arrayPool.Return(oldMemoryBlock, NeedsClearing);
}
_memoryBlock = newMemoryBlock;
_capacity = newMemoryBlock.Length;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Add(T item)
{
int requiredCapacity = _count + 1;
if (_capacity < requiredCapacity)
if (_count == _capacity)
{
_ = RecyclableListHelpers<T>.EnsureCapacity(this, requiredCapacity);
ResizeAndCopy();
// & WAS SLOWER
// new Span<T>(_memoryBlock)[_count++] = item;
// }
// else
// if (_capacity == _count)
// {
// break;
// case false:
// newCapacity = requestedCapacity;
// list._memoryBlock = Resize(list, newCapacity);
// break;
// }
// var newCapacity = list._memoryBlock.Length;
// _capacity = _memoryBlock.Length;
}
// & WAS SLOWER
// new Span<T>(_memoryBlock)[_count++] = item;
// new Span<T>(_memoryBlock, _count++, 1)[0] = item;
_memoryBlock[_count++] = item;
_version++;
}
@@ -321,6 +413,7 @@ namespace Recyclable.Collections
int blockIndex,
sourceBlockSize = items.BlockSize,
// TODO: Convert to bit-shift
lastBlockIndex = (int)(sourceItemsCount / sourceBlockSize) - 1;
for (blockIndex = 0; blockIndex <= lastBlockIndex; blockIndex++)
@@ -502,7 +595,7 @@ namespace Recyclable.Collections
{
_capacity = 0;
// If anything, it has been already cleared by .Clear(), Remove() or RemoveAt() methods, as the list was modified / disposed.
_arrayPool.Return(_memoryBlock, false);
RecyclableListHelpers<T>._arrayPool.Return(_memoryBlock, false);
}
GC.SuppressFinalize(this);
@@ -86,7 +86,7 @@ namespace Recyclable.CollectionsTests
}
// Validate
_ = list.Capacity.Should().Be(0);
_ = list.Capacity.Should().Be(4);
_ = list.Count.Should().Be(0);
_ = list.Should().BeEmpty();
}