AndanteSoft.ArrayPoolCollection.MemoryPack 1.2.1

dotnet add package AndanteSoft.ArrayPoolCollection.MemoryPack --version 1.2.1
                    
NuGet\Install-Package AndanteSoft.ArrayPoolCollection.MemoryPack -Version 1.2.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="AndanteSoft.ArrayPoolCollection.MemoryPack" Version="1.2.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="AndanteSoft.ArrayPoolCollection.MemoryPack" Version="1.2.1" />
                    
Directory.Packages.props
<PackageReference Include="AndanteSoft.ArrayPoolCollection.MemoryPack" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add AndanteSoft.ArrayPoolCollection.MemoryPack --version 1.2.1
                    
#r "nuget: AndanteSoft.ArrayPoolCollection.MemoryPack, 1.2.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#addin nuget:?package=AndanteSoft.ArrayPoolCollection.MemoryPack&version=1.2.1
                    
Install AndanteSoft.ArrayPoolCollection.MemoryPack as a Cake Addin
#tool nuget:?package=AndanteSoft.ArrayPoolCollection.MemoryPack&version=1.2.1
                    
Install AndanteSoft.ArrayPoolCollection.MemoryPack as a Cake Tool

ArrayPoolCollection

A low-allocation collection library using pooled arrays

<a href="https://www.nuget.org/packages/AndanteSoft.ArrayPoolCollection">NuGet Version</a> <a href="LICENSE">GitHub License</a> <a href="https://www.nuget.org/packages/AndanteSoft.ArrayPoolCollection">NuGet Downloads</a>

Logo

Basic Usage

// By using the `using` statement, it will be automatically returned to the pool.
using var dict = new ArrayPoolDictionary<int, string>();

// Can be used in the same way as a Dictionary
dict.Add(123, "Alice");
Console.WriteLine(dict[123]);   // "Alice"

Install

ArrayPoolCollection can be installed from NuGet AndanteSoft.ArrayPoolCollection.

dotnet add package AndanteSoft.ArrayPoolCollection

ArrayPoolCollection requires .NET Standard 2.1 or .NET 9. (There are performance benefits to using .NET 9 where possible.)

If you want to use it with MemoryPack, install AndanteSoft.ArrayPoolCollection.MemoryPack instead.

Unity

Supported version: 2021.2 or later. (API Compatibility Level: .NET Standard 2.1)

My test environment is 6000.1.0b1.

Use NuGetForUnity to install.

Features

It provides the following collections, which are mostly compatible with the default collections:

They use ArrayPool<T> for their internal arrays, and by calling Dispose() when destroyed, they do not generate GC garbage. These implement almost all APIs available as of .NET 9.

Therefore, it can also be used to utilize PriorityQueue, which was not available in the current Unity environment.

Example of Dictionary usage:

using var dict = new ArrayPoolDictionary<string, string>(new Utf8BytesComparer())
{
    { "Alice", "Supernova" }
};

// You can use GetAlternateLookup in Unity.
// Unfortunately, it is not a `allows ref struct` constraint, so
// `ReadOnlySpan<byte>` cannot be taken as an argument.
Debug.Log(dict["Alice"]);   // "Supernova"
Debug.Log(dict.GetAlternateLookup<byte[]>()[Encoding.UTF8.GetBytes("Alice")]);  // "Supernova"

private class Utf8BytesComparer : IEqualityComparer<string>, IAlternateEqualityComparer<byte[], string>
{
    public string Create(byte[] alternate)
    {
        return Encoding.UTF8.GetString(alternate);
    }

    public bool Equals(byte[] alternate, string other)
    {
        return Encoding.UTF8.GetBytes(other).SequenceEqual(alternate);
    }

    public bool Equals(string x, string y)
    {
        return x.Equals(y);
    }

    public int GetHashCode(byte[] alternate)
    {
        return Encoding.UTF8.GetString(alternate).GetHashCode();
    }

    public int GetHashCode(string obj)
    {
        return obj.GetHashCode();
    }
}

In addition, it implements the following alternative collections, although they have some differences in specifications:

Reason: It's an old collection and the design is old. For example, And and Xor returns void ​​to indicate that they are mutable. Also, BitArray only implements ICollection (non-generic!), while ArrayPoolBits implements IList<bool>.

Also implement an object pool:

  • ObjectPool

By utilizing this and reusing instances of ArrayPool***, GC garbage can be completely eliminated. This is also available in the pool of GameObjects in Unity.

// Get a dictionary from the pool
var dict = ObjectPool<ArrayPoolDictionary<int, string>>.Shared.Rent();

// The contents of the rented dictionary are undefined, so clear it.
dict.Clear();

// Then use it just like a normal Dictionary.
dict.Add(123, "Alice");
dict.Add(456, "Barbara");
dict.Add(789, "Charlotte");

Debug.Log($"{dict[123]}");      // "Alice"

// Return it when you're done using it
ObjectPool<ArrayPoolDictionary<int, string>>.Shared.Return(dict);

Example of pooling GameObject in Unity:

var root = new GameObject("root");

// Create GameObjectPool
using var gameObjectPool = new ObjectPool<GameObject>(PooledObjectCallback<GameObject>.Create(
    // OnInstantiate - create prefab
    () =>
    {
        var go = GameObject.CreatePrimitive(PrimitiveType.Cube);
        go.transform.SetParent(root.transform, false);
        return go;
    },
    // OnRent - set active
    go => go.SetActive(true),
    // OnReturn - set inactive
    go => go.SetActive(false),
    // OnDestroy - destroy
    go => Destroy(go)
));

// Generate a specified number of pieces in advance
gameObjectPool.Prewarm(16);

for (int i = 0; i < 64; i++)
{
    // Rent & Return
    var go = gameObjectPool.Rent();
    go.transform.position = UnityEngine.Random.insideUnitSphere * 10;
    UniTask.Delay(1000).ContinueWith(() => gameObjectPool.Return(go)).Forget();

    await UniTask.Delay(50);
}

await UniTask.Delay(1000);

The DebugArrayPool facilitates debugging in the following situations:

  1. Forget to return - pool.DetectLeaks() and show stacktrace
  2. Return and continue to use - Initializing/fuzzing when Return()
  3. Renter expects pre cleared array - Fuzzing when Rent()
  4. Returner does not request a clearArray - Automatically detects reference type at Return()
  5. Double return - Throws exception

However, since the overhead for obtaining the stacktrace is very large, its use in a production environment is not recommended.

SlimArrayPool is a more flexible ArrayPool. ArrayPool has problems such as low performance when lending out multiple arrays of the same size, and the limited number of arrays that can be pooled. In addition, in the Unity environment, the implementation is old, so the upper limit of the pooled array size is small (2^20). It can be used without any problems even in such situations. However, to improve performance, there are no debugging functions, just like with ArrayPool.

Therefore, it is a good idea to use the following implementation to switch between Debug and Release:

using ArrayPoolCollection.Pool;
using System;
using UnityEngine;

#nullable enable

public class SwitchArrayPool<T>
{
    private static IBufferPool<T[]>? m_Pool;

    public static IBufferPool<T[]> Shared
    {
        get
        {
            if (m_Pool is null)
            {
#if DEBUG
                var pool = new DebugBufferPool<T[], T>(new ArrayPoolPolicy());
                Application.wantsToQuit += () =>
                {
                    try
                    {
                        pool.DetectLeaks();
                    }
                    catch (ObjectDisposedException ex)
                    {
                        Debug.LogException(ex);
                    }
                    return true;
                };
#else
                var pool = new SlimBufferPool<T[], T>(new ArrayPoolPolicy());
#endif
                m_Pool = pool;
            }

            return m_Pool;
        }
    }

    private class ArrayPoolPolicy : IBufferPoolPolicy<T[], T>
    {
        public Span<T> AsSpan(T[] value) => value.AsSpan();
        public T[] Create(int length) => new T[length];
    }
}

This can also be applied to create pools of NativeArrays or unmanaged resources.

It also includes an efficient implementation of IBufferWriter<T> that utilizes a pool.

  • ArrayPoolBufferWriter<T> : Suitable for small or fixed size buffers.
  • ArrayPoolSegmentedBufferWriter<T> : Suitable for objects of variable length and larger than megabytes.

For usage, see the With MemoryPack section.

With MemoryPack

Collections can be serialized using MemoryPack.

// Call this once at the beginning
ArrayPoolCollectionRegisterer.Register();

using var list = new ArrayPoolList<string>()
{
    "Alice", "Barbara", "Charlotte",
};

// Create IBufferWriter<byte>
using var writer = new ArrayPoolBufferWriter<byte>();

// Serialize the list
MemoryPackSerializer.Serialize(writer, list);

// Deserialize the list
var deserialized = MemoryPackSerializer.Deserialize<ArrayPoolList<string>>(writer.WrittenSpan);

// "Alice, Barbara, Charlotte"
Debug.Log(string.Join(", ", deserialized));

Notes:

  • ArrayPoolPriorityQueue do not implement IEnumerable<T>, so serialization requires the [MemoryPackAllowSerialize] attribute.
  • Comparer of Dictionary/HashSet is not usually serializable, so it is not saved. If you want to save it, you need to overwrite it when serializing.
using var ignoreCase = new ArrayPoolDictionary<string, int>(StringComparer.OrdinalIgnoreCase)
{
    { "Alice", 16 },
};

byte[] bytes = MemoryPackSerializer.Serialize(ignoreCase);

// ---

var deserialized = new ArrayPoolDictionary<string, int>(StringComparer.OrdinalIgnoreCase);
MemoryPackSerializer.Deserialize(bytes, ref deserialized);

Debug.Log($"{deserialized["alice"]}");      // 16

Fork

Build

dotnet build

Run tests

dotnet test

Run benchmarks

dotnet run -c Release --project ArrayPoolCollection.Benchmarks

Publish

dotnet pack

License

MIT License

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed. 
.NET Core netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.1 is compatible. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
1.2.1 91 a month ago
1.2.0 99 a month ago
1.1.1 97 a month ago
1.1.0 96 2 months ago
1.0.0 115 2 months ago