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:
1 parent
11fd6e3dd0
commit
b9181eee23
3 files changed
+221
-50
No files matched your search
@@ -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)]
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user