【问题标题】:Unsafe.As from byte array to ulong arrayUnsafe.As 从字节数组到 ulong 数组
【发布时间】:2017-03-09 12:10:51
【问题描述】:

我目前正在考虑移植我的 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;

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

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

【问题讨论】:

  • 欢迎来到这里,第一个问题很好!
  • 您正在做的是通过结构进行转换的旧“技巧”(这个stackoverflow.com/a/35841815/613130)...如果您检查state.Length,您会发现它是“错误的” .
  • 你发现的仍然非常有趣的库:-)
  • 感谢您的回答,我知道显式结构技巧,但实际上并没有建立连接,它实际上与 Unsafe.As 相同。
  • @Tornhoof Unsafe.As&lt;&gt; 不需要这个技巧,但最终它会做同样的事情。它重新解释作为参数传递给另一种类型的内容。在 ILAsm 中这很容易:ldarg.0; ret :-) 无事可做 :-)

标签: c# c#-7.0


【解决方案1】:

在当前的 .NET 术语中,这非常适合 Span&lt;T&gt;

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&lt;T&gt; 的长度变化是正确的;如果您想使用 SIMD 进行硬件加速,也可以将其转换为 Span&lt;Vector&lt;T&gt;&gt;

【讨论】:

    【解决方案2】:

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

    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
    

    【讨论】:

    • 关于长度问题:Span&lt;T&gt; 可以在这里提供帮助 - 请参阅我的答案
    • @MarcGravell 是的,这是更安全的选择,尽管我相信由于长度转换等原因会慢一点。不过,我还没有对差异进行基准测试。只要他小心翼翼,他也许可以从他现在的方式中获得更多的表现。我完全爱上了新的跨度/内存/不安全的东西,它让我想去重写一切。
    【解决方案3】:

    C# 支持“固定缓冲区”,我们可以这样做:

        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# 中原始类型的一维数组始终存储为连续的内存块,这就是为什么获取(托管)数组的地址是可以的)。

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

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

    并根据需要操作/访问字节。

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

    【讨论】:

    • “你也可以”——不!不要那样做;一旦你离开fixed 块,指针就不可靠了;实际上它会很少移动,但是:它可以(GC);永远不要从fixed 块传递指针向外(向内很好)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-06-10
    • 2011-10-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-03-09
    相关资源
    最近更新 更多