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