C# - Improving Array Performance with .NET 7

vercidium

Vercidium

Posted on February 10, 2023

C# - Improving Array Performance with .NET 7

.NET 7 introduced a new static class called NativeMemory, which can be used to allocate unmanaged memory.

Normally when working with arrays in C#, the .NET runtime manages the allocation of memory and frees it when it's no longer used, hence the name managed memory.

When using NativeMemory, you're on your own. The .NET garbage collector won't free it when you no longer need it, and the .NET runtime won't perform bounds checks when reading or writing to it. The advantage is it's faster to work with as the bounds checks performed by .NET aren't present.

Warning! Allocating 1024 integers and then writing to the 1025th one will overwrite memory that may be in use elsewhere. Be careful!

Usage

Allocate a 1kb block of unmanaged memory:

var byteCount = 1024;
void* data = NativeMemory.Alloc(byteCount);
Enter fullscreen mode Exit fullscreen mode

Allocate and zero a block of unmanaged memory:

void* data = NativeMemory.AllocZeroed(1024);
Enter fullscreen mode Exit fullscreen mode

Resize a block of unmanaged memory to 2kb:

NativeMemory.Realloc(data, 2048);
Enter fullscreen mode Exit fullscreen mode

Free a block of unmanaged memory:

NativeMemory.Free(data);
data = null;
Enter fullscreen mode Exit fullscreen mode

Example - Accumulating an Array

Using managed memory:

// Allocate managed memory
const int LENGTH = 1024;
int[] data = new int[LENGTH];


// Accumulate
int total = 0;

for (int i = 0; i < LENGTH ; i++)
    total += data[i];


// Log the result
Console.WriteLine(total);
Enter fullscreen mode Exit fullscreen mode

Using unmanaged memory:

// Allocate unmanaged memory
const int LENGTH = 1024;
int byteCount = LENGTH * sizeof(int);
int* data = (int*)NativeMemory.Alloc(byteCount);


// Option 1
// - Same syntax as the managed example
int total = 0;

for (int i = 0; i < LENGTH; i++)
    total += data[i];


// Option 2
// - Different syntax, same functionality
for (int i = 0; i < LENGTH; i++)
    total += *(data + i);


// Option 3 - 
// - Make a copy of the data pointer, read from it, then increment it
// - This works because pointers are essentially a 32 or 64 bit
//   integer, meaning we can increment and compare them
var readPtr = data;
var endPtr = data + LENGTH;

while (readPtr < endPtr)
{
    total += *readPtr;
    readPtr++;
}


// Log the result
Console.WriteLine(total);
Enter fullscreen mode Exit fullscreen mode

Benchmarks

The above managed and unmanaged examples were benchmarked using BenchmarkDotNet, with an array length of 32768.

Time required to accumulate a managed array vs an unmanaged array. 12.538us average time for managed, 9.616us average time for unmanaged.

💖 💪 🙅 🚩
vercidium
Vercidium

Posted on February 10, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related