Some RecyclableList properties made public to allow use in RecyclableArrayList. Added support for RecyclableList in RecyclableArrayList.AddRange. Additional tests added for RecyclableArrayList.
Recyclable.Collections
Recyclable.Collections project is an open source framework for operating dynamic lists at performance close to raw arrays, but fairly unlimited in size. It aims at providing minimal memory footprint. It implements IList<T>'s interface and is targeted as direct replacements of List<T>, SortableList<T>, PriorityQueue<T> & similar.
Included
RecyclableArrayList<T>RecyclableList<T>RecyclableSortableList<T>- Range:
long - Interfaces:
IList<T>,ILongList<T>,IEnumerable<T>,IDisposable
- Range:
RecyclableQueue<T>- Range:
long - Interfaces:
IList<T>,ILongList<T>,IEnumerable<T>,IDisposable
- Range:
RecyclableStack<T>- Range:
long - Interfaces:
IList<T>,ILongList<T>,IEnumerable<T>,IDisposable
- Range:
Characteristicts of the classes
Common
- All classes implement
IDisposableinterface & SHOULD be disposed after use. That's to return block arrays taken from the shared pool. It may be foreseen as an issue for replacement in existing code, which obviously is missingusingclause. But considering thatDisposewill be called byGCanyway, it should cause issues in specific scenarios, only. In either case the fix is one-word addition ofusing. - Memory blocks are created in 2 ways, depending on
blockSizevalue- when
blockSize < 100⇨ blocks are created as regulararrays - when
blockSize >= 100⇨ blocks are taken from and returned toArrayPool<T>.Shared, when blocks are removed
- when
- Array pools are shared between the same
Ttype. I.e.List<int>will use a different pool fromList<short>and so on. For high concurrency environments you may want to provide your own pools, when this feature becomes available in the upcoming releases. - Trying to access
this[int index],this[long index],Count,LongCountetc. whenCapacity == 0will raiseNullReferenceException. This is by design to remove all non-critical code from the constructor.
RecyclableArrayList<T>
- Range:
int - Interfaces:
IList<T>,IEnumerable<T>,IDisposableThis is the direct equivalent ofList<T>class, except that the arrays are taken from the shared pool, instead of directly being allocated. It proves to out performList<T>& the standard arrays in certains operations and is fairly close in others. But they may perform worse in certain scenarios. If you're interested in details, please refer to benchmarks.
RecyclableList<T>, RecyclableSortableList<T>, RecyclableQueue<T>, RecyclableStack<T>
- Range:
long - Interfaces:
IList<T>,ILongList<T>,IEnumerable<T>,IDisposable - All classes provide large storage capabilities, in practice limited only by the memory available in your system.
- All data is stored in blocks, with the provided
int blockSizeor the default, currently being10,240items in each block. It's recommended to use smaller numbers, if you foresee storing low no. of items on the list. - There is no memory copying to increase the capacity of the recyclable classes, anymore. If more capacity is needed, a new memory block is allocate & added to the internal list of blocks. This is the only list that may re-allocate memory block & copy their content to grow. That shouldn't be an issue considering limited no. of blocks stored by each class in practical scenarios. Currently I'm not planning any works around that. Shall there be a need to eliminate it, you're welcomed to file a PR with the proposed changes.
Insert,Remove&RemoveAtmethods raiseNotSupportedException. This is by design in the current version, because it involves performance degradadion. I'm planning works around this in the upcoming weeks. If you need that sooner, you're welcomed to file a PR with the proposed changes.
Architecture Decisions
The architecture of the classes included in this project is driven primarily by performance & memory footprints. The following decisions were made to support it.
Code duplication
There is high level of code duplication in each of the classes. When you look at them, you will see they are alike & some of the code could be extracted to extension methods. It has been done & has proven to perform worse. For this reason, properties & methods expected to be on the hot-path, duplicate much of the code to get the most out of it.
Common base - ILongList<T>
Having a common base class for all the classes looks very attractive. But it is also proved to perform worse, if you start making properties & methods virtual & there are subsequent calls to base. For this reason, each of the classes is the root class & there are hardly any virtual methods & properties. All classes are meant to be final, but I've not marked them as sealed. If you feel that you'd benefit from it, you're welcomed to inherit from them. The protected stuff is there for your use.
If you desire to have a common base for calls, each of the classes implements IList<T> & ILongList<T> interfaces. ILongList<T> is a new interface, made alike IList<T>, but unlocking full support for long indexing & use. Considering the characteristics of the classes I feel that the generic name is justified, instead of IRecyclableList<T> what you could expect.
Protection against inccorrect use & state
Having protection against invalid state of the object is an important part of software engineering. The classes included in this project provide the same level of protection as the regular array & List class. They will raise exceptions when invalid index is given. But there is no additional protection that the content & state of the objects internally used by these classes are valid. You can break them, if you want to.
This decision is driven by performance measures. Considering wide use of the lists on the hot-paths, each additional check & operation starts counting. I've decided to eliminate all the checks for the sake of performance. If you feel that you need additional protection, feel free to inherit or reuse any of the provided classes to provide that.
this[int index], Count, IndexOf(T item) etc. may overflow, but...
All the classes included in this package support long indexing and addressing. The LongCount property will always provide the total no. of items stored on the list. Since Count is just LongCount typecasted to int, its value may overflow.
But List<T> addressing is kind of currently limited to int ranges anyway in practical scenarios. As such Count overflowing shouldn't be an issue when using the classes with existing code. If you use Count in your loops, it's recommended to replace it with LongCount, tough. You don't need to make any changes in foreach or other iterator loops. They are expected to transparently use the new classes & yield all items, beyond int.MaxValue range.
This is by design, for the best performance. Initially there have been checks in place to prevent overflowing & return int.MaxValue instead, but they proved to slow down the operations. Considering low risk for existing code, I've decided to take it out.
If you foresee the overflowing as an issue, you're welcomed to propose an improvement & file a PR with proposed solution.
Auto-properties & new language features
When you look at the code you may note, that it looks like in early c# days. Where the new language features yield the same or better performance, they are & will be used.
However, many of the new language features have proved to provide worse performance at some point in my testing. It includes auto-properties, for which I could see getters & setters generated in IL, instead of fields. Because all the classes are expected to be on the hot-paths, I've eliminated most of them.
If you know & can show it in your benchmarks that the code can be simplified without impacting the performance, feel free to file a PR with proposed changes. Please include benchmark results from your testing to speed up things.
Thread Safety
The public static members of the classes are thread safe. Any instance members are not guaraneteed to be thread safe. You need to implement locking mechanism to safely use the classes in a multi-threading environment.
Use
All the classes included in this package are meant to be direct replacements of their corresponding system classes.
IList<T>⇨ILongList<T>List<T>⇨RecyclableList<T>Queue<T>⇨RecyclableQueue<T>PriorityQueue<T>⇨RecyclableSortedList<T>SortedList<T>⇨RecyclableSortedList<T>Stack<T>⇨RecyclableStack<T>
Examples of each of the above classes are provided below. Please refer automated tests for more.
RecyclableArrayList<T>
It's the closest equivalent to List<T> in regards to functionality. It supports items' removal & delivers the best performance. But it's limited to int range, tough. If you find that acceptable, my recommendation is to use this class as the direct replacement for List<T>.
Do note, that it implements ILongList<T> interface, too. This is to allow having common code base regardless, if you use the basic RecyclableArrayList<T> or it's long-range version - RecyclableList<T>. By doing that you can switch between them with highly limited changes in code.
RecyclableList<T>
public void RecyclableListExample()
{
int[] testNumbers = new[] { 1, 2, 2, 3 };
// Create
using var list = new RecyclableList<int>();
// Add items
foreach (var index in testNumbers)
{
list.Add(index);
}
foreach (var item in list)
{
Console.WriteLine(item);
}
// This will iterate over this[long index]
for (var itemIdx = 0; itemIdx <= list.LongCount; itemIdx++)
{
Console.WriteLine(list[itemIdx]);
}
// This will iterate over this[int index] and may overflow if there are > int.MaxValue no. of items on the list. See: Architecture Decisions
for (var itemIdx = 0; itemIdx <= list.LongCount; itemIdx++)
{
Console.WriteLine(list[itemIdx]);
}
list.Clear();
}