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.
Allocating simple arrays
Let’s start with a simple sample to allocate multiple arrays in a loop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} |
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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
unsafe private static void ShowAddress(string name, int[] item) | |
{ | |
fixed (int* addr = item) | |
{ | |
Console.Write($"\t0x{(ulong)addr:X}"); | |
} | |
} |
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
private static void UsingSimpleArraysWithGC() | |
{ | |
Console.WriteLine(nameof(UsingSimpleArraysWithGC)); | |
for (int i = 0; i < 20; i++) | |
{ | |
GC.Collect(0); | |
LocalUseOfArray(i); | |
} | |
Console.WriteLine(); | |
Console.WriteLine(); | |
} |
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} |
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.
Thanks, this looks useful.
I wonder why it is .NET Core only?
LikeLike
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.
LikeLike
This all cool stuff. I didn’t know something this beneficial existed!
Thanks for sharing.
LikeLike