检查类型是否为blittable的最快方法?

问题描述 投票:15回答:5

在我的序列化/反序列化器中,我有以下代码段:

    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的最快方法是什么?

c# serialization marshalling value-type blit
5个回答
5
投票

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

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 { }
    }
}

7
投票

当前的答案适用于提问者的情况,但根据规范,blittable值类型的数组本身也是blittable类型。扩展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));
}

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


2
投票

@IllidanS4在这个页面上的优秀代码错误地返回false的数组,其中元素是一个blittable formatted type,这意味着该数组也是blittable。从这个例子开始,我解决了这个问题,并为一些错误处理的案例增加了处理,例如:

  • T[]其中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;
}

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


0
投票

使用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)

0
投票

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

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

GCHandle.Alloc每次都使用相对昂贵的操作锁来分配或重用现有的插槽。静态readonly原始类型在jitting时变为常量,但不会将GCHandle存储在泛型类型中,因为每个通用的即时创建都将采用自己的副本。

© www.soinside.com 2019 - 2024. All rights reserved.