C# CopyBlock 与 CopyBlockUnaligned 用例

C# CopyBlock vs CopyBlockUnaligned use cases

所以我正在学习加密哈希函数及其具体实现。由于 C# 是我的首选语言,我决定为 .NET Core 创建一个加密库。迟早我不得不使用一些不安全的代码来进行一些性能优化。

不出所料,我必须复制大量内存。到目前为止,我一直使用 Unsafe.CopyBlock() 来做这个。但是,似乎还有一个名为 Unsafe.CopyBlockUnaligned() 的函数。遗憾的是,documentation 并没有详细说明两者之间的区别(除了 Unsafe.CopyBlockUnaligned() “不假定地址的体系结构相关对齐。”

现在我想知道:选择 Unsafe.CopyBlock()Unsafe.CopyBlockUnaligned() 有什么好处,或者它们有完全不同的用例吗?如果是这样,我应该什么时候使用哪个?如果有人可以解释两者之间的差异或向我指出一些文档,那就太好了。

Unsafe.CopyBlock()Unsafe.CopyBlockUnalinged() 两次磕磕绊绊之后,我终于决定看一看 the source of System.Runtime.CompilerServices.Unsafe

比较CopyBlock()

的IL
.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
}

代码为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
}

我们可以看到,唯一明显的区别是在 Unsafe.CopyBlockUnaligned() 实现中调用 cpblk 之前的 unaligned. 0x1。由于我不太熟悉 IL,所以看一下 the documentation of System.Reflection.Emit.OpCodes.Unaligned 揭示了 unaligned 关键字的定义:

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.

再往下说

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).

因此可以肯定地说 Unsafe.CopyBlock() 只应在我们知道源地址和目标地址都对齐时才使用,目前 only inofficially includes addresses returned by Marshal.AllocHGLobal(). However the upcoming .NET 6 (currently in preview) will bring us NativeMemory 我们自己控制内存对齐,这将允许我们使用Unsafe.CopyBlock()更自由。因此,如果不确定对齐方式,建议只使用 Unsafe.CopyBlockUnaligned()