Unsafe reference arithmetic in .NET

By | 2022-03-07

Taking a closer look at the CompilerServices Unsafe approach to unsafe reference arithmetic over fixed statement in .NET.

.NET has a garbage collector (GC) that automates memory management. This is incompatible with working with direct memory pointers, since the GC needs to track object usage and move objects around when required. You are of course free to allocate memory manually from the operating system and work on direct pointers, and as a friendly gesture you can tell the GC how much memory you allocate/de-allocate so that it knows when to respond to memory pressure.

You can however do reference arithmetic to access memory that is managed by the GC. This used to be done with fixed statements, but .NET Core with its many awesome improvements also offer a better way to do reference arithmetic.

Note that this kind of operation is considered “unsafe”, and even requires both compiler to allow unsafe code as well as unsafe keyword on class or method. The “unsafe” part of this comes from the fact that there are no bounds check.

From MSDN: In the common language runtime (CLR), unsafe code is referred to as unverifiable code. Unsafe code in C# is not necessarily dangerous; it is just code whose safety cannot be verified by the CLR. The CLR will therefore only execute unsafe code if it is in a fully trusted assembly. If you use unsafe code, it is your responsibility to ensure that your code does not introduce security risks or pointer errors.

Using fixed statement

This example will extract element i from array using fixed statement for unsafe memory pointer:

If we take a look at the X64 ASM output we see that this is fairly straight forward pointer arithmetic.

Using System.Runtime.CompilerServices.Unsafe

There is another method to achieve the same result, using System.Runtime.CompilerServices.Unsafe.

Looking at the x64 ASM we see that it is a bit cleaner.

Implementation of Unsafe class

While fixed statement was a language feature handled by the compiler, the implementation of Unsafe class is done in IL. Implementation details can be seen here.

For example the two methods AsPointer and Read used in example above:

Benchmarks

The difference between the two methods is negligible, and a benchmark is pointless. But why not, for fun. Less CPU instructions should translate to better performance. But the difference are so small and error margin is subject how it is used.

The benchmark summarizes 1 million integers from an int[] array with 1M items (4MB) using linear access. The time per item is just above 0.5ns (5×10-7 ms / 0.0000005 ms), somewhere around 1.800.000.000 operations per second.

Conclusion

Unsafe.* provides a way to work with reference pointers in managed memory using managed code. It removes the need for a fixed scope and allows for returning ref pointers to data types in memory.

The compiler output from using Unsafe.AsPointer and Unsafe.Add is cleaner than the fixed statement counterpart, but does not yield any measurable performance difference.

Leave a Reply

Your email address will not be published.