【问题标题】:The fastest way to check if a type is blittable?检查一个类型是否是 blittable 的最快方法?
【发布时间】:2012-05-21 10:11:37
【问题描述】:

在我的序列化器/反序列化器中,我有以下 sn-p:

    if (element_type.IsValueType && collection_type.IsArray)
    {
        try
        {
            GCHandle h = GCHandle.Alloc(array_object, GCHandleType.Pinned);
            int arrayDataSize = Marshal.SizeOf(element_type) * c.Count;
            var array_data = new byte[arrayDataSize];
            Marshal.Copy(h.AddrOfPinnedObject(), array_data, 0, arrayDataSize);
            h.Free();
            WriteByteArray(array_data);

            return;
        }
        catch (ArgumentException)
        {
            //if the value type is not blittable, then we need to serialise each array item one at a time
        }
    }

其目的是尝试以最有效的方式将值类型数组写入流中(即,仅将内容作为一串字节)。

当类型是值类型但不是 blittable 并且 Alloc() 失败时,问题就出现了。目前,异常被捕获并将控制传递给处理数组的代码,就好像它由引用类型组成一样。

然而,由于我的应用程序中遇到的值类型的数量,这个检查(由于我理解的异常的抛出和捕获非常慢)被证明是一个严重的瓶颈。所以我想知道,检查一个类型是否是 blittable 的最快方法是什么?

【问题讨论】:

  • 我遇到了同样的问题,我最终缓存了每种类型的结果(例如在静态字典中)。检查与此处相同,try/catch。

标签: c# serialization marshalling value-type blit


【解决方案1】:

使用http://msdn.microsoft.com/en-us/library/system.type.islayoutsequential.aspxhttp://msdn.microsoft.com/en-us/library/system.type.isexplicitlayout.aspx

element_type.IsValueType && collection_type.IsArray && (element_type.IsLayoutSequential || element_type.IsExplicitLayout)

【讨论】:

  • 谢谢,但不幸的是这不起作用。 IsLayoutSequential 属性对于我尝试过的至少一种非 blittable 类型(带有字符串的简单结构)为 true。
【解决方案2】:

我正在使用泛型类来缓存结果。测试以相同的方式完成(尝试分配固定句柄)。

public static class BlittableHelper<T>
{
    public static readonly bool IsBlittable;

    static BlittableHelper()
    {
        try
        {
            // Class test
            if (default(T) != null)
            {
                // Non-blittable types cannot allocate pinned handle
                GCHandle.Alloc(default(T), GCHandleType.Pinned).Free();
                IsBlittable = true;
            }
        }
        catch { }
    }
}

【讨论】:

  • 缓存是我最终做的,虽然我认为你的缓存技术是我见过的最有效的!
  • 请注意,这不适用于 Mono,因为 GCHandle.Alloc 不会为非 blittable 类型抛出异常。见github.com/mono/mono/pull/4533
  • @JayLemmon 如果您使用的是 Unity,则有 UnsafeUtility.IsBlittable。否则你可能不得不递归地“走遍田野”。
  • 这表示int[] 不是 blittable,尽管docs.microsoft.com/en-us/dotnet/framework/interop/… 明确表示一维整数数组是。我在这里遗漏了什么,还是default(T) != null 检查需要去? (根据相同的参考资料,在某些情况下,只有 blittable 成员的类可以是 blittable,这取决于它的编组方式。)
  • @MattTsōnto int 数组的内容是 blittable,但对数组本身的引用(存储在 int[] 变量中)是不可 blittable。
【解决方案3】:

当前答案适用于提问者的情况,但根据规范,可位值类型数组本身也是可位类型。稍微扩展了 Ondřej 的方法,因此它考虑到了这一点,并且也适用于引用类型:

public static bool IsBlittable<T>()
{
    return IsBlittableCache<T>.Value;
}

public static bool IsBlittable(Type type)
{
    if(type.IsArray)
    {
        var elem = type.GetElementType();
        return elem.IsValueType && IsBlittable(elem);
    }
    try{
        object instance = FormatterServices.GetUninitializedObject(type);
        GCHandle.Alloc(instance, GCHandleType.Pinned).Free();
        return true;
    }catch{
        return false;
    }
}

private static class IsBlittableCache<T>
{
    public static readonly bool Value = IsBlittable(typeof(T));
}

作为副作用,这会为string 返回(尽管正确)false,因为GetUninitializedObject 无法创建它。假设 Alloc 确实检查了 blittability(string 除外),这应该是可靠的。

【讨论】:

  • 这将返回falseint[],但它仍然是可blittable。从!elem.IsValueType 中删除 NOT 以修复 :)
  • @FooBarTheLittle 谢谢!
  • @IllidanS4supportsMonica:这无法检测到已为编组设置的结构,例如StructLayout(LayoutKind.Sequential)MarshalAs() 每个字段的属性。另一方面,涉及Marshal.SizeOf() 的测试,使用任意数量的技术创建该大小的非托管缓冲区,然后检查Marshal.PtrToStructure() 是否成功?你怎么看?
  • @ulatekh Blittable 并不意味着可编组。您首先在字段上设置 MarshalAs 的事实表明这样的结构不能是 blittable。
  • @IllidanS4supportsMonica:很公平......我想我的需求略有不同。感谢您的澄清。
【解决方案4】:

@IllidanS4 在此页面上的出色代码错误地返回 false 用于元素是 blittable formatted type 的数组,这意味着该数组也是 blittable。从那个例子开始,我修复了这个问题,并增加了对一些处理不当的情况的处理,例如:

  • T[] where T :格式化类型(刚刚提到)
  • 锯齿状数组int[][][]...
  • 枚举(但不是System.Enum 本身)
  • 接口、抽象类型
  • 泛型类型(永不 blittable)。

我还添加了避免昂贵的 Exception 块的案例更加详尽,并对我能想到的所有不同类型的类型进行了单元测试。

public static bool IsBlittable(this Type T)
{
    while (T.IsArray)
        T = T.GetElementType();

    bool b;
    if (!((b = T.IsPrimitive || T.IsEnum) || T.IsAbstract || T.IsAutoLayout || T.IsGenericType))
        try
        {
            GCHandle.Alloc(FormatterServices.GetUninitializedObject(T), GCHandleType.Pinned).Free();
            b = true;
        }
        catch { }
    return b;
}

应按原样使用其他答案中的不错的缓存机制。

【讨论】:

  • 检查其他类型的好主意。只有一个小错误,boolchar,虽然是原始的,但不是 blittable(大小取决于平台)。锯齿状数组也不应该是 blittable,因为它们是对象引用的数组。根据MSDN,多维数组也不是,尽管我的代码有同样的问题。
【解决方案5】:

最快的方法不是分配而是重用现有的 GCHandle,例如:

var gch = GCHandle.Alloc(null, GCHandleType.Pinned);
gch.Target = new byte[0];
gch.Target = "";

GCHandle.Alloc 每次都分配或重用现有的插槽,并获取相对昂贵的操作锁。并且静态只读原始类型在 jitting 时变为常量,但不要将 GCHandle 存储在泛型类型中,因为每个泛型实例化都会获取自己的副本。

【讨论】:

    【解决方案6】:

    我没有足够的声誉来添加评论,所以我会写我的评论作为答案:

    我已经测试了@IS4 提出的代码,他的函数说字符串不是 blittable,这是正确的。然而,当在 Unity 中使用 Mono 后端时,他的实现还说带有字符串字段的结构是 blittable(这是不正确的)。

    我还测试了 Unity 的 UnsafeUtility.IsBlittable() 函数,它为这些结构返回正确的值,所以如果我们想实现一个在 Mono 上正常工作的 IsBlittable() 函数,我认为我们别无选择,只能使用反射以确保结构中的所有字段也是 blittable。

    我已经使用 Mono 脚本后端在 Unity 2017.4 和 Unity 2018.4 中测试了这个实现,它似乎在我迄今为止尝试过的所有类型上都能正常工作:

    using System;
    using System.Reflection;
    using System.Runtime.Serialization;
    using System.Runtime.InteropServices;
    
    public static class BlittableHelper
    {
    #if UNITY_2018_1_OR_NEWER || UNITY_2019_1_OR_NEWER || UNITY_2020_1_OR_NEWER
        // If we're using Unity, the simplest solution is using
        // the built-in function
        public static bool IsBlittableType(Type type)
        {
            return Unity.Collections.LowLevel.Unsafe.UnsafeUtility.IsBlittable(
                type
            );
        }
    #else
        // NOTE: static properties are not taken into account when
        // deciding whether a type is blittable, so we only need
        // to check the instance fields and properties.
        private static BindingFlags Flags =
        BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
    
        public static bool IsBlittableType(Type type)
        {
            // According to the MSDN, one-dimensional arrays of blittable
            // primitive types are blittable.
            if (type.IsArray)
            {
                // NOTE: we need to check if elem.IsValueType because
                // multi-dimensional (jagged) arrays are not blittable.
                var elem = type.GetElementType();
                return elem.IsValueType && IsBlittableType(elem);
            }
    
            // These are the cases which the MSDN states explicitly
            // as blittable.
            if
            (
                type.IsEnum
                || type == typeof(Byte)
                || type == typeof(SByte)
                || type == typeof(Int16)
                || type == typeof(UInt16)
                || type == typeof(Int32)
                || type == typeof(UInt32)
                || type == typeof(Int64)
                || type == typeof(UInt64)
                || type == typeof(IntPtr)
                || type == typeof(UIntPtr)
                || type == typeof(Single)
                || type == typeof(Double)
            )
            {
                return true;
            }
    
    
            // These are the cases which the MSDN states explicitly
            // as not blittable.
            if
            (
                type.IsAbstract
                || type.IsAutoLayout
                || type.IsGenericType
                || type == typeof(Array)
                || type == typeof(Boolean)
                || type == typeof(Char)
                //|| type == typeof(System.Class)
                || type == typeof(Object)
                //|| type == typeof(System.Mdarray)
                || type == typeof(String)
                || type == typeof(ValueType)
                || type == typeof(Array)
                //|| type == typeof(System.Szarray)
            )
            {
                return false;
            }
    
    
            // If we've reached this point, we're dealing with a complex type
            // which is potentially blittable.
            try
            {
                // Non-blittable types are supposed to throw an exception,
                // but that doesn't happen on Mono.
                GCHandle.Alloc(
                    FormatterServices.GetUninitializedObject(type),
                    GCHandleType.Pinned
                ).Free();
    
                // So we need to examine the instance properties and fields
                // to check if the type contains any not blittable member.
                foreach (var f in type.GetFields(Flags))
                {
                    if (!IsBlittableTypeInStruct(f.FieldType))
                    {
                        return false;
                    }
                }
    
                foreach (var p in type.GetProperties(Flags))
                {
                    if (!IsBlittableTypeInStruct(p.PropertyType))
                    {
                        return false;
                    }
                }
    
                return true;
            }
            catch
            {
                return false;
            }
        }
    
        private static bool IsBlittableTypeInStruct(Type type)
        {
            if (type.IsArray)
            {
                // NOTE: we need to check if elem.IsValueType because
                // multi-dimensional (jagged) arrays are not blittable.
                var elem = type.GetElementType();
                if (!elem.IsValueType || !IsBlittableTypeInStruct(elem))
                {
                    return false;
                }
    
                // According to the MSDN, a type that contains a variable array
                // of blittable types is not itself blittable. In other words:
                // the array of blittable types must have a fixed size.
                var property = type.GetProperty("IsFixedSize", Flags);
                if (property == null || !(bool)property.GetValue(type))
                {
                    return false;
                }
            }
            else if (!type.IsValueType || !IsBlittableType(type))
            {
                // A type can be blittable only if all its instance fields and
                // properties are also blittable.
                return false;
            }
    
            return true;
        }
    #endif
    }
    
    // This class is used as a caching mechanism to improve performance.
    public static class BlittableHelper<T>
    {
        public static readonly bool IsBlittable;
    
        static BlittableHelper()
        {
            IsBlittable = BlittableHelper.IsBlittableType(typeof(T));
        }
    }
    

    【讨论】:

    【解决方案7】:

    netcore2.0 开头的System.Runtime.CompilerServices.RuntimeHelpers.IsReferenceOrContainsReferences&lt;T&gt; 可以让您检查类型是否可blittable

    static bool IsBlittable<T>()
       => !RuntimeHelpers.IsReferenceOrContainsReferences<T>();
    
    static bool IsBlittable(Type type)
    {
        return (bool)typeof(RuntimeHelpers)
                   .GetMethod(nameof(RuntimeHelpers.IsReferenceOrContainsReferences))
                   .MakeGenericMethod(type)
                   .Invoke(null, null);
    }
    

    我使用这个实现通过网络发送数组

    ValueTask SendAsync<T>(T[] array, CancellationToken token) where T : unmanaged
    {
         // zero allocations, no <AllowUnsafeBlocks> required
         return _stream.WriteAsync(MemoryMarshal.AsBytes((ReadOnlySpan<T>)array, token);
    }
    

    Unmanaged 约束强制使用 blittable 类型。 Reference

    【讨论】:

    • 这给出了不正确的结果。例如,它声称 bool 是 blittable 而 int[] 不是。
    • @MattTsōnto ,OP 寻找一种将通用数据写入流的方法,而不是 COM 互操作。所以我认为这对于序列化场景是正确的(就C#F# 发现的正确而言),但对于COM-interop 则不然。也许术语 blittable 不是正确的
    • @JL0PD:OP 希望在使用 GCHandle.Alloc 时避免异常,即使在 完全不受管理的 但仍然是 non-blittable 类型上,例如boolcharDateTimedecimal 等。这与 COM 互操作无关。问题不在于如何检查一个值类型是否可以安全地序列化,而是GCHandle.Alloc 拒绝固定一些非 blittable 对象,即使它们可以安全地序列化。
    【解决方案8】:

    这对我有用:

    static bool IsBlittable(Type t)
    {
      if (t.IsPrimitive) return true; if (!t.IsValueType) return false;
      var a = t.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
      for (int i = 0; i < a.Length; i++) if (!IsBlittable(a[i].FieldType)) return false;
      return true;
    }
    

    【讨论】:

    • 这给出了不正确的结果。例如,它说 bool 是 blittable,而 int[] 不是。
    【解决方案9】:

    这里有一个替代方案,它只是对Microsoft's documentation 所说内容的直接表示。它不短,但它比这里的其他解决方案正确处理了更多的案例。如果您担心反射调用的性能,可以将其包装在一个简单的缓存中。

    static bool IsBlittable(Type type)
        => IsBlittablePrimitive(type)
        || IsBlittableArray(type)
        || IsBlittableStruct(type)
        || IsBlittableClass(type);
    static bool IsBlittablePrimitive(Type type)
        => type == typeof(byte)
        || type == typeof(sbyte)
        || type == typeof(short)
        || type == typeof(ushort)
        || type == typeof(int)
        || type == typeof(uint)
        || type == typeof(long)
        || type == typeof(ulong)
        || type == typeof(System.IntPtr)
        || type == typeof(System.UIntPtr)
        || type == typeof(float)
        || type == typeof(double)
        ;
    static bool IsBlittableArray(Type type)
        => type.IsArray
        && type.GetArrayRank() == 1
        && IsBlittablePrimitive(type.GetElementType())
        ;
    static bool IsBlittableStruct(Type type)
        => type.IsValueType
        && !type.IsPrimitive
        && type.IsLayoutSequential
        && type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public).All(IsBlittableField);
    static bool IsBlittableClass(Type type)
        => !type.IsValueType
        && !type.IsPrimitive
        && type.IsLayoutSequential
        && type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public).All(IsBlittableField);
    static bool IsBlittableField(FieldInfo field)
        => IsBlittablePrimitive(field.FieldType) 
        || IsBlittableStruct(field.FieldType);
    

    测试用例:

    Is blittable?
    - Int32: True
    - Int32[]: True
    - Int32[,]: False
    - Int32[][]: False
    - String: False
    - String[]: False
    - Boolean: False
    - String: False
    - Byte[]: True
    - struct X { public int x; }: True
    - struct Y { public int[] Foo { get; set; } }: False
    - class CAuto { public int X { get; set; } }: False
    - [StructLayout(LayoutKind.Sequential)]class CSeq { public int X { get; set; } }: True
    

    注意:这会将 Span 报告为 blittable,这在我看来不太可能,但我不确定。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-04-29
      • 2016-08-02
      • 2011-01-11
      • 1970-01-01
      • 2010-11-24
      • 2011-07-13
      • 1970-01-01
      相关资源
      最近更新 更多