This library ArrayPool provides fast array cache processing.
It is more limited than the .Net standard System.Buffers.ArrayPool, but it is faster.
| Environment | Version |
|---|---|
| Unity | 2021.3.38f1, 2022.3.20f1 |
| .Net | 4.x, Standard 2.1 |
| Process | Time |
|---|---|
| Get_Legacy | 0.4604 ms |
| Get_Fast | 0.2526 ms |
| Get_ConcurrentPool | 0.37965 ms |
| Get_ThreadStaticPool | 0.2832 ms |
| Return_Legacy | 4.47095 ms |
| Return_Fast | 0.21675 ms |
| Return_ConcurrentPool | 0.59515 ms |
| Return_ThreadStaticPool | 0.20135 ms |
Get shows more than 2X performance improvement and Return shows more than 20X performance improvement.
private readonly ref struct Measure
{
private readonly string _label;
private readonly StringBuilder _builder;
private readonly float _time;
public Measure(string label, StringBuilder builder)
{
_label = label;
_builder = builder;
_time = (Time.realtimeSinceStartup * 1000);
}
public void Dispose()
{
_builder.AppendLine($"{_label}: {(Time.realtimeSinceStartup * 1000) - _time} ms");
}
}
:
var log = new StringBuilder();
Stack<int[]> arrs = new Stack<int[]>(CacheCount);
using (new Measure("Get_Legacy", log))
{
for (int i = 0; i < CacheCount; ++i)
{
arrs.Push(ArrayPool<int>.Shared.Rent(32));
}
}
using (new Measure("Return_Legacy", log))
{
foreach (var arr in arrs)
{
ArrayPool<int>.Shared.Return(arr);
}
}
:| Process | Mono | IL2CPP |
|---|---|---|
| Get_Legacy | 10.59292 ms | 43.74219 ms |
| Get_Fast | 0.3275452 ms | 0.1464844 ms |
| Get_ConcurrentPool | 0.3051529 ms | 0.1708984 ms |
| Get_ThreadStaticPool | 0.296154 ms | 0.3076172 ms |
| Return_Legacy | 4.778381 ms | 37.94922 ms |
| Return_Fast | 0.1763153 ms | 0.1230469 ms |
| Return_ConcurrentPool | 0.5840683 ms | 0.8242188 ms |
| Return_ThreadStaticPool | 0.1847572 ms | 0.1679688 ms |
The IL2CPP environment is nearly 400X faster.
Install the following packages
- Open [Window > Package Manager].
- click [+ > Add package from git url...].
- Type
https://github.com/Katsuya100/ArrayPool.git?path=packagesand click [Add].
The above method may not work well in environments where git is not installed.
Download the appropriate version of com.katuusagi.arraypool.tgz from Releases, and then [Package Manager > + > Add package from tarball...] Use [Package Manager > + > Add package from tarball...] to install the package.
Download the appropriate version of Katuusagi.ArrayPool.unitypackage from Releases and Import it into your project from [Assets > Import Package > Custom Package].
You can use ArrayPool with the following notation.
When using ArrayPool, please return an array if possible.
If not returned, the cache in the Pool will be reduced, which may lead to performance degradation.
int[] arr = ArrayPool<int, _32>.Get();
Debug.Log(arr.Length); // 32
ArrayPool<int, _32>.TryReturn(arr);If it is troublesome to return, the following notation is also valid.
using(ArrayPool<int, _32>.Get(out var arr))
{
Debug.Log(arr.Length); // 32
}Use ConcurrentArrayPool if you want to use it in a multi-threaded environment.
int[] arr = ConcurrentArrayPool<int, _32>.Get();The ConcurrentArrayPool has a unique Pool different from the ArrayPool.
This allows it to be used in multi-threaded environments.
However, it has performance issues compared to ArrayPool.
Specifically, allocation occurs during TryReturn.
I plan to improve this in a later update.
Using ThreadStaticArrayPool allows for multi-threading without performance loss.
int[] arr = ThreadStaticArrayPool<int, _32>.Get();This may consume more memory than ConcurrentArrayPool because it uses a different pool for each Thread.
Also, be careful not to return the Get array in a different thread.
The return will be completed successfully, but will be returned in a different pool than the pool from which it was retrieved.
You can create a cache in advance by calling the MakeCache method.
Creating a cache in advance allows you to determine the cache size and suppress allocation on the first run.
ArrayPool<int, _32>.MakeCache(32);System.Buffers.ArrayPool allows you to specify the array length as a variable, but this incurs an overhead to get the Pool.
Katuusagi.Pool.ArrayPool allows the array length to be specified as a constant from a generic argument.
Therefore, it can be retrieved quickly using Static Type Caching.
The first time access to the cache, an allocation to build the cache is performed, but if the cache is built in advance, this allocation will be zero.
Since the MethodImpl attribute is set to AggressiveInline, you can also expect optimization by inline expansion at build time.
The above techniques provide overwhelming performance compared to System.Buffers.ArrayPool.