Unsafe.As 从字节数组到 ulong 数组

Unsafe.As from byte array to ulong array

我目前正在考虑移植我的 metro hash implementon 以使用 C#7 功能,因为多个部分可能会受益于 ref locals 以提高性能。 散列对 ulong[4] 数组进行计算,但结果是 16 byte 数组。目前我正在将 ulong 数组复制到结果 byte 缓冲区,但这需要一些时间。 所以我想知道 System.Runtime.CompilerServices.Unsafe 在这里使用是否安全:

var result = new byte[16];
ulong[] state = Unsafe.As<byte[], ulong[]>(ref result);
ref var firstState = ref state[0];
ref var secondState = ref state[1];
ulong thirdState = 0;
ulong fourthState = 0;

上面的代码片段意味着我也将结果缓冲区用于我的部分状态计算,而不仅仅是用于最终输出。

我的单元测试成功,根据 benchmarkdotnet 跳过块复制会导致 20% 性能提升,这足以让我确定它是否正确使用它。

C# 支持 "fixed buffers",这是我们可以做的事情:

    public unsafe struct Bytes
    {
        public fixed byte bytes[16];
    }

然后

    public unsafe static Bytes Convert (long[] longs)
    {
        fixed (long * longs_ptr = longs)
              return *((Bytes*)(longs_ptr));
    }

试试吧。 (C# 中基本类型的一维数组总是存储为连续的内存块,这就是为什么获取(托管)数组的地址很好)。

您甚至可以 return 指针以获得更快的速度:

    public unsafe static Bytes * Convert (long[] longs)
    {
        fixed (long * longs_ptr = longs)
        return ((Bytes*)(longs_ptr));
    }

和 manipulate/access 所需的字节数。

        var s = Convert(longs);
        var b = s->bytes[0];

你所做的似乎很好,只是要小心,因为没有什么可以阻止你这样做:

byte[] x = new byte[16];
long[] y = Unsafe.As<byte[], long[]>(ref x);

Console.WriteLine(y.Length); // still 16

for (int i = 0; i < y.Length; i++)
    Console.WriteLine(y[i]); // reads random memory from your program, could cause crash

在当前的 .NET 术语中,这非常适合 Span<T>:

Span<byte> result = new byte[16];
Span<ulong> state = MemoryMarshal.Cast<byte, ulong>(result);

这强制执行长度等,同时具有良好的 JIT 行为 不需要 unsafe。您甚至可以 stackalloc 原始缓冲区(从 C# 7.2 开始):

Span<byte> result = stackalloc byte[16];
Span<ulong> state = MemoryMarshal.Cast<byte, ulong>(result);

注意 Span<T> 得到的长度变化是正确的;如果您想使用 SIMD 进行硬件加速,则转换为 Span<Vector<T>> 也很简单。