C# CopyBlock vs CopyBlockUnaligned use cases

Viewed 436

So I am learning about cryptographic hash functions and their implementations in particular. As C# is my language of choice I decided to create a crypto library for .NET Core. Sooner or later I had to use some unsafe code for some performance optimizations.

As to be expected I have to copy a lot of memory around. Up until now I always used Unsafe.CopyBlock() for this. However there also seems to be a function called Unsafe.CopyBlockUnaligned(). Sadly the documentation isn't too detailed about the differences between the two (except that Unsafe.CopyBlockUnaligned() "doesn't assume architecture dependent alignment of the addresses.")

Now I'm wondering: Are there any benefits in choosing for example Unsafe.CopyBlock() over Unsafe.CopyBlockUnaligned() or do they have completely different use cases? If so when should I use which? It'd be nice if someone could explain the differences between the two or point me to some documentation.

1 Answers

After stumbling about Unsafe.CopyBlock() and Unsafe.CopyBlockUnalinged() a second time I finally decided to take a look at the source of System.Runtime.CompilerServices.Unsafe.

Comparing the IL of CopyBlock()

.method public hidebysig static void CopyBlock(void* destination, void* source, uint32 byteCount) cil managed aggressiveinlining
{
    .custom instance void System.Runtime.Versioning.NonVersionableAttribute::.ctor() = ( 01 00 00 00 )
    .maxstack 3
    ldarg.0
    ldarg.1
    ldarg.2
    cpblk
    ret
}

with the code of CopyBlockUnaligned()

.method public hidebysig static void CopyBlockUnaligned(void* destination, void* source, uint32 byteCount) cil managed aggressiveinlining
{
    .custom instance void System.Runtime.Versioning.NonVersionableAttribute::.ctor() = ( 01 00 00 00 )
    .maxstack 3
    ldarg.0
    ldarg.1
    ldarg.2
    unaligned. 0x1
    cpblk
    ret
}

We can see that the only noticable difference is the unaligned. 0x1 right before the call to cpblk in the Unsafe.CopyBlockUnaligned() implementation. As I'm not too fluent in IL taking a look at the documentation of System.Reflection.Emit.OpCodes.Unaligned reveals the definition of the unaligned keyword:

Indicates that an address currently atop the evaluation stack might not be aligned to the natural size of the immediately following ldind, stind, ldfld, stfld, ldobj, stobj, initblk, or cpblk instruction.

and further down it states

Unaligned specifies that the address (an unmanaged pointer, native int) on the stack might not be aligned to the natural size of the immediately following ldind, stind, ldfld, stfld, ldobj, stobj, initblk, or cpblk instruction. That is, for a Ldind_I4 instruction the alignment of the address may not be to a 4-byte boundary. For initblk and cpblk the default alignment is architecture dependent (4-byte on 32-bit CPUs, 8-byte on 64-bit CPUs).

Therefore it is safe to say that Unsafe.CopyBlock() should only be used when we know that both source and destination addresses are aligned which currently only inofficially includes addresses returned by Marshal.AllocHGLobal(). However the upcoming .NET 6 (currently in preview) will bring us NativeMemory to control memory alignment ourselves which would allow us to use Unsafe.CopyBlock() more freely. So if unsure about the alignment just using Unsafe.CopyBlockUnaligned() is recommended.

Related