【问题标题】:Get unsafe pointer to array of KeyValuePair<DateTime,decimal> in C#在 C# 中获取指向 KeyValuePair<DateTime,decimal> 数组的不安全指针
【发布时间】:2015-09-30 10:51:48
【问题描述】:

我有 KeyValuePair&lt;DateTime,decimal&gt; 的大数组。我知道在内存中数组是连续的,因为 KVP 是一种值类型,DateTime 实际上是一个 Int64,而十进制是一个 4 个整数的数组(并且不会改变)。但是,DateTime 不是 blittable,decimal 不是原始的。

是否有任何方法来滥用类型系统并获取指向数组的不安全指针并将其作为字节处理? (GCHandle.Alloc 不能与这两种类型一起使用,因为它们是结构的一部分,但可以与这些类型的数组一起使用。)

(如果您对为什么感兴趣,我现在手动将数组转换为我认为是 1 对 1 字节 [] 表示,而且速度很慢)

【问题讨论】:

    标签: c# .net datetime


    【解决方案1】:

    最后还有一个公共工具:System.Runtime.CompilerServices.Unsafe package。

    以下是通过测试:

    using System.Runtime.CompilerServices.Unsafe;
    [Test]
    public unsafe void CouldUseNewUnsafePackage() {
        var dt = new KeyValuePair<DateTime, decimal>[2];
        dt[0] = new KeyValuePair<DateTime, decimal>(DateTime.UtcNow.Date, 123.456M);
        dt[1] = new KeyValuePair<DateTime, decimal>(DateTime.UtcNow.Date.AddDays(1), 789.101M);
        var obj = (object)dt;
        byte[] asBytes = Unsafe.As<byte[]>(obj);
        //Console.WriteLine(asBytes.Length); // prints 2
        fixed (byte* ptr = &asBytes[0]) {
            // reading this: https://github.com/dotnet/coreclr/issues/5870
            // it looks like we could fix byte[] and actually KeyValuePair<DateTime, decimal> will be fixed
            // because:
            // "GC does not care about the exact types, e.g. if type of local object 
            // reference variable is not compatible with what is actually stored in it, 
            // the GC will still track it fine."
            for (int i = 0; i < (8 + 16) * 2; i++) {
                Console.WriteLine(*(ptr + i));
            }
            var firstDate = *(DateTime*)ptr;
            Assert.AreEqual(DateTime.UtcNow.Date, firstDate);
            Console.WriteLine(firstDate);
            var firstDecimal = *(decimal*)(ptr + 8);
            Assert.AreEqual(123.456M, firstDecimal);
            Console.WriteLine(firstDecimal);
            var secondDate = *(DateTime*)(ptr + 8 + 16);
            Assert.AreEqual(DateTime.UtcNow.Date.AddDays(1), secondDate);
            Console.WriteLine(secondDate);
            var secondDecimal = *(decimal*)(ptr + 8 + 16 + 8);
            Assert.AreEqual(789.101M, secondDecimal);
            Console.WriteLine(secondDecimal);
        }
    }
    

    【讨论】:

    • 安全的确切构造仍然有点不清楚。 of course, you have to know what you are doing 他不建议这样做。我现在很乐意在生产中使用您的特定代码,但这只是因为我不知道它怎么会出错。这是证明它是安全的弱证据......
    • 我复制的评论告诉我什么时候它是“安全的”,我向here请求.NET团队的确认。
    • 评论说某个特定的问题不相关。它并不是说代码是完全安全的。事实上,它引入了一个特定的问题(内存布局),并暗示代码未来可能不兼容。此外,除了这段特定的代码,我们仍然不知道不安全操作是安全的规则。
    • @usr 仍然比__makeref-like 的东西好。当我确定运行时的行为方式时,我已经使用条件 #if NET451 有时,因此可以在具有巨大性能优势的此类条件中使用它。
    • @riki 3D 数组具有不同的对象标头,因此您必须至少不安全地转换为 byte[,]。但也许还有别的东西。
    【解决方案2】:

    我刚刚测试了unsafeGCHandle.Alloc 不起作用(如您所建议的那样)。仍然有一个非常不安全的黑客可以做到这一点。我不知道这对于当前的 CLR 是否安全。它当然不能保证将来会起作用。

    您可以将任何类型的对象引用转换为 IL 中的任何其他引用类型。该 IL 将无法验证。 JIT 倾向于接受相当多的不可验证的结构。也许这是因为他们想支持托管 C++。

    所以需要生成一个DynamicMethod,大致有以下IL:

    static T UnsafeCast(object value) {
     ldarg.1 //load type object
     ret //return type T
    }
    

    我认为这应该可行...

    或者,您可以使用反射调用System.Runtime.CompilerServices.JitHelpers.UnsafeCast&lt;T&gt;

    这是一个危险的工具...我不会在生产代码中使用它。

    【讨论】:

    • IL 来救援!谢谢!为什么在内存布局已知且不会改变的特定情况下是危险的?
    • 这可能会打乱 GC 和 JIT 优化。您将 T2 存储在类型为 T1 的变量中。谁知道这会产生什么样的后果?!请注意,这可以使用 byte[] 和 sbyte[] 支持的方式来实现。因此,至少在一种情况下 CLR 已经支持这一点。
    • CLR byte[] 和 sbyte[] 在哪里被转换?
    • (sbyte[])(object)new byte[1] 编译并运行。当您调用GetType() 时,您会从sbyte[] 变量中获得byte[]。这违反了 C# 规范,但 CLR 允许这样做。
    • 谢谢!我无法通过反射访问 System.Runtime.CompilerServices.JitHelpers.UnsafeCast,但 IL 有效。如果我将继续返回 Tinput object 并反向销毁它们 - 第一个 T 实例,然后是原始实例 - 你认为仍然存在破坏整个 CLR 的风险吗?我只需要避免复制然后按索引访问 T 数组,就好像我使用了不安全和指针强制转换一样。
    猜你喜欢
    • 1970-01-01
    • 2013-06-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-12-29
    • 1970-01-01
    相关资源
    最近更新 更多