Array Pool

If you have an application where a lot of arrays are created and destroyed, the garbage collector has some work to do. To avoid this, you can use the ArrayPool with .NET Core. ArrayPool manages a pool of arrays, arrays can be rented from and returned to the pool.

Array Pool - or just a Night Pool

Allocating simple arrays

Let’s start with a simple sample to allocate multiple arrays in a loop.


private static void UsingSimpleArrays()
{
Console.WriteLine(nameof(UsingSimpleArrays));
for (int i = 0; i < 20; i++)
{
LocalUseOfArray(i);
}
Console.WriteLine();
Console.WriteLine();
}
private static void LocalUseOfArray(int i)
{
int[] arr = new int[ARRAYSIZE];
ShowAddress($"simple array {i}", arr);
FillTheArray(arr);
UseTheArray(arr);
}

view raw

ArrayPool.cs

hosted with ❤ by GitHub

In the loop, 20 int arrays with a size of ARRAYSIZE are allocated, filled and used by the dummy methods FillTheArray and UseTheArray. Running the sample app, ARRAYSIZE is set to 1000.

To print the array, the method ShowAddress is used. This method makes use of unsafe C# code and pointers to directly access the address of the array:


unsafe private static void ShowAddress(string name, int[] item)
{
fixed (int* addr = item)
{
Console.Write($"\t0x{(ulong)addr:X}");
}
}

view raw

ArrayPool.cs

hosted with ❤ by GitHub

In the method LocalUseOfArray, the array is allocated. The variable arr gets out of scope when the method ends. However, because Array is a reference type, the array stays allocated until the garbage collector (GC) releases the object. As there’s not really much memory needed by the complete application, and my system has enough, it’s very unlikely that the GC runs and releases the memory of the array. Running the application, it can be seen easily that every new allocation of the array results in a new memory address:

0x11E00029440 0x11E0002ACD8 0x11E0002BDA0 0x11E0002CE68 0x11E0002DF30 0x11E0002EFF8 0x11E000300C0 0x11E00031188 0x11E00032250 0x11E00033318 0x11E000343E0 0x11E000354A8 0x11E00036570 0x11E00037638 0x11E00038700 0x11E000397C8 0x11E0003A890 0x11E0003B958 0x11E0003CA20 0x11E0003DAE8

With .NET Core, every time you run the application, you’ll get different addresses used.

Using the Garbage Collector to release memory


private static void UsingSimpleArraysWithGC()
{
Console.WriteLine(nameof(UsingSimpleArraysWithGC));
for (int i = 0; i < 20; i++)
{
GC.Collect(0);
LocalUseOfArray(i);
}
Console.WriteLine();
Console.WriteLine();
}

view raw

ArrayPool.cs

hosted with ❤ by GitHub

Running the app while calling the GC before every new array allocation, the result is different. In this simple app, it’s always the same address for the array returned:

0x11E0000F9B0 0x11E0000F9B0 0x11E0000F9B0 0x11E0000F9B0 0x11E0000F9B0 0x11E0000F9B0 0x11E0000F9B0 0x11E0000F9B0 0x11E0000F9B0 0x11E0000F9B0 0x11E0000F9B0 0x11E0000F9B0 0x11E0000F9B0 0x11E0000F9B0 0x11E0000F9B0 0x11E0000F9B0 0x11E0000F9B0 0x11E0000F9B0 0x11E0000F9B0 0x11E0000F9B0

In a real application, you shouldn’t invoke the Collect method of the GC explicitely. The runtime usually handles the collection more efficiently.

Using a shared ArrayPool

Next, let’s get rid of the 20 array allocations and use an array pool instead. The Rent method of the ArrayPool class returns an array. With the argument an array size is passed to the Rent method, and this method returns an array with at least this number of elements. The static Shared property of ArrayPool returns an ArrayPool instance that is shared. Instead of using the shared pool, you can also create a separate pool for a specific requiremeent instead. After usage, the array is returned to the pool with the Return method, and the memory can be re-used. Optional, you can clear the memory content before returning it to the pool


private static void LocalUseOfSharedPool(int i)
{
int[] arr = ArrayPool<int>.Shared.Rent(ARRAYSIZE);
ShowAddress($"simple array {i}", arr);
FillTheArray(arr);
UseTheArray(arr);
ArrayPool<int>.Shared.Return(arr);
}

view raw

ArrayPool.cs

hosted with ❤ by GitHub

Running the application, because the array is always returned to the pool before a new array is requsted, the pool can always return the same memory:

0x11E00012AF0 0x11E00012AF0 0x11E00012AF0 0x11E00012AF0 0x11E00012AF0 0x11E00012AF0 0x11E00012AF0 0x11E00012AF0 0x11E00012AF0 0x11E00012AF0 0x11E00012AF0 0x11E00012AF0 0x11E00012AF0 0x11E00012AF0 0x11E00012AF0 0x11E00012AF0 0x11E00012AF0 0x11E00012AF0 0x11E00012AF0 0x11E00012AF0

With this, garbage collection does not need to run. The memory used by the ArrayPool is reused.

Summary

In case you need to allocate and release a lot of arrays, it can be more efficient to use the ArrayPool class. It’s easy to use with Renting memory from the pool, and Returning it back. With this you remove a burden from the garbage collector and you can increase performance with your application.

Have fun learning and writing code,
Christian

More information on .NET Core is in my book Professional C# 6 and .NET Core 1.0, and in my workshops.

6 thoughts on “Array Pool

    1. Darren, all the new things are coming for .NET Core 🙂

      However, I just tried it – you can use ArrayPool with the .NET Framework as well. The NuGet package is for .NET Standard 1.1.

      Like

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.