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>>
也很简单。
我目前正在考虑移植我的 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>>
也很简单。