Fast, low-allocation ports of List, Dictionary, HashSet, Stack, and Queue using ArrayPool and Span.
$ dotnet add package Collections.Pooled.V2The original project has not shown any signs of life for a while and is still targeting the very old .NET Core 2.1. I forked this repo and retargeted it to .NET Standard 2.1 since I will only be using it for new development in .NET Core projects. If it helps you - great! I'll do my best to try and respond to any issues/inquiries.
This library is based on classes from System.Collections.Generic that have been altered
to take advantage of the new System.Span<T> and System.Buffers.ArrayPool<T> libraries
to minimize memory allocations, improve performance, and/or allow greater interoperablity
with modern API's.
Collections.Pooled.V2 supports .NET Standard 2.1 (.NET Core 3.0+). An extensive set of unit tests and benchmarks have been ported from corefx.
Collections.Pooled.Tests
Tests in group: 27567
Total Duration: 4.1 sec
Outcomes
27567 Passed
Install-Package Collections.Pooled.V2
dotnet add package Collections.Pooled.V2
PooledList<T>PooledList<T> is based on the corefx source code for System.Collections.Generic.List<T>,
modified to use ArrayPool for internal array-storage allocation, and to support Span<T>.
There are some API changes worth noting:
Find and FindLast have become TryFind and TryFindLast, following the standard
.NET "try" pattern.PooledList<T>.Span property returns a Span<T> over the portion of the internal
array store that is populated. This span can be further sliced, or passed into other methods
that can read from or write to a span.
GetRange now returns a Span<T>. For example, foo.GetRange(5, 100) is equivalent to foo.Span.Slice(5, 100).CopyTo now takes a Span<T> and doesn't offer any start-index or count parameters. Use the Span property and slicing instead.AddRange and InsertRange can now accept a ReadOnlySpan<T>.AddSpan and InsertSpan methods ensure the internal storage has the capacity
to add the requested number of items, and return a Span<T> that can be used to write
directly to that section of the internal storage. Caveats about "collection modified during enumeration"
checks apply here as well.Predicate<T> and Converter<T1, T2> have been replaced with standard Func<> equivalents.List<T> (you will still benefit from pooling of intermediate arrays as the PooledList is resized).ToPooledList() extension methods are provided.ArrayPool<T> to the constructor, to be used instead of the
default ArrayPool<T>.Shared pool.clearMode constructor parameter gives you control over whether data is cleared before returning
arrays to the ArrayPool.sizeToCapacity constructor parameter causes the list to start out with Count == Capacity.
All entries in the list will have the default value for the type, or if clearMode is set to ClearMode.Never
then entries in the list may have a previously-used value from the array pool. This feature is primarily useful
when working with value types and avoiding unnecessary allocations.Adding items to a list is one area where ArrayPool helps us quite a bit:
PooledDictionary<TKey, TValue>PooledDictionary<TKey, TValue> is based on the corefx source code for System.Collections.Generic.Dictionary<TKey, TValue>,
modified to use ArrayPool for internal storage allocation, and to support Span<T>.
There are some API changes worth noting:
AddRange, GetOrAdd, AddOrUpdateKeyValuePair<TKey, TValue> objects, or a sequence of
ValueTuple<TKey, TValue> objects.Dictionary<TKey, TValue> (you will still benefit from pooling of intermediate arrays as the PooledDictionary is resized).ToPooledDictionary() extension methods are provided.ClearMode constructor parameter gives you control over whether data is cleared before returning
arrays to the ArrayPool.Adding to dictionaries is where using ArrayPool really has an impact:
PooledSet<T>PooledSet<T> is based on the corefx source code for System.Generic.Collections.HashSet<T>,
modified to use ArrayPool for internal storage allocation, and to support ReadOnlySpan<T> for all set functions.
ReadOnlySpan<T>.HashSet<T> (you will still benefit from pooling of intermediate arrays as the PooledSet is resized).ToPooledSet() extension methods are provided.ClearMode constructor parameter gives you control over whether data is cleared before returning
arrays to the ArrayPool.Here's what pooling does for us when adding to a PooledSet.
PooledStack<T>PooledStack<T> is based on the corefx source code for System.Generic.Collections.Stack<T>,
modified to use ArrayPool for internal storage allocation.
Stack<T> is the addition of the RemoveWhere method: because sometimes you just need to remove
something from a stack.Stack<T> (you will still benefit from pooling of intermediate arrays as the PooledStack is resized).ToPooledStack() extension methods are provided.ArrayPool<T> to the constructor, to be used instead of the
default ArrayPool<T>.Shared pool.ClearMode constructor parameter gives you control over whether data is cleared before returning
arrays to the ArrayPool.Once again, pushing to a stack shows off some of the advantages of using ArrayPool:
PooledQueue<T>PooledQueue<T> is based on the corefx source code for System.Generic.Collections.Queue<T>,
modified to use ArrayPool for internal storage allocation.
Queue<T> is the addition of the RemoveWhere method: because sometimes you just need to remove
something from a queue.Queue<T> (you will still benefit from pooling of intermediate arrays as the PooledQueue is resized).ToPooledQueue() extension methods are provided.ArrayPool<T> to the constructor, to be used instead of the
default ArrayPool<T>.Shared pool.ClearMode constructor parameter gives you control over whether data is cleared before returning
arrays to the ArrayPool.PooledMemory<T>PooledMemory<T> is an IMemoryOwner<T> implementation that uses ArrayPool<T> for internal storage.
Unlike directly renting from ArrayPool<T>, the exposed memory region is always constrained to exactly the requested length rather than the entire rented buffer.
Memory<T> and Span<T> views into the pooled buffer, with the logical length fixed to the requested count.this[int index]) and implements IEnumerable<T> for simple element access.count (pre-allocated size).ReadOnlySpan<T> or IEnumerable<T> (copying contents).ClearMode and/or a custom ArrayPool<T>.ClearMode allows you to control whether arrays are cleared before returning to the pool:
Always: always clear.Never: never clear.Auto: clear only when T is a reference type or contains references.ArrayPool<T> implementation if you don’t want to use the shared global pool.