The fastest way to check if a type is blittable?

旧城冷巷雨未停 提交于 2019-12-10 00:53:21

问题


In my serialiser/deserialiser, I have the following snippet:

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

The purpose of which is to try and write an array of value types to a stream, in the most efficient way possible (that is, just the content as a bunch of bytes).

The problem comes when the type is a value type but not blittable, and Alloc() fails. At the moment the exception is caught and control passed to code which deals with the array as if it consisted of reference types.

This check however (due to the throwing and catching of the exception which I understand is very slow) is proving to be a severe bottleneck due to the number of value types that are encountered in my application. So I am wondering, what is the fastest way to check if a type is blittable?


回答1:


I'm using generic class to cache results. Test is done in same way (trying to allocate pinned handle).

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



回答2:


The current answer works for the questioner's case but, according to the specification, arrays of blittable value types are also blittable types themselves. Extended Ondřej's method a bit, so it takes this into account, and also works for reference types:

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));
}

As a side effect, this returns (albeit correctly) false for string, because GetUninitializedObject can't create it. Assuming Alloc really checks for blittability (except for string), this should be reliable.




回答3:


The excellent code by @IllidanS4 on this page incorrectly returns false for arrays where the element is a blittable formatted type, meaning that the array is blittable also. Starting from that example, I fixed that problem and added handling for a few more mishandled cases, such as:

  • T[] where T : formatted-type (just mentioned)
  • jagged arrays int[][][]...
  • enums (but not System.Enum ittself)
  • interfaces, abstract types
  • generic types (never blittable).

I also added made the cases for avoiding the expensive Exception block a bit more exhaustive and ran unit tests for all the different kinds of types I could think of.

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

The nice caching mechanism from the other answer should be used as-is.




回答4:


Use http://msdn.microsoft.com/en-us/library/system.type.islayoutsequential.aspx and http://msdn.microsoft.com/en-us/library/system.type.isexplicitlayout.aspx:

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



回答5:


Fastest way would be not allocating but reusing existing GCHandle like:

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

GCHandle.Alloc allocates or reuses existing slot each time with taking locks which is relatively expensive operation. And static readonly primitive types becomes constants when jitting but dont store GCHandle in generic type because each generic instantination will take its own copy.



来源:https://stackoverflow.com/questions/10574645/the-fastest-way-to-check-if-a-type-is-blittable

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!