簡體   English   中英

檢查類型是否為 blittable 的最快方法?

[英]The fastest way to check if a type is blittable?

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

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

當前的答案適用於提問者的情況,但根據規范,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));
}

作為副作用,這會為string返回(盡管正確) false ,因為GetUninitializedObject無法創建它。 假設Alloc真的檢查 blitability (除了string ),這應該是可靠的。

我正在使用泛型類來緩存結果。 測試以相同的方式完成(嘗試分配固定句柄)。

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

@IllidanS4 在此頁面上的優秀代碼錯誤地為元素為 blittable 格式類型的數組返回false ,這意味着該數組也是 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;
}

應按原樣使用來自其他答案的不錯的緩存機制。

我沒有足夠的聲譽來添加評論,所以我會寫下我的評論作為答案:

我已經測試了@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));
    }
}

netcore2.0開始,有System.Runtime.CompilerServices.RuntimeHelpers.IsReferenceOrContainsReferences<T>允許您檢查類型是否為 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類型。 參考

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

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

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

GCHandle.Alloc分配或重用現有插槽並獲取鎖,這是相對昂貴的操作。 靜態只讀原始類型在 jitting 時變為常量,但不要將 GCHandle 存儲在泛型類型中,因為每個泛型實例化都將采用自己的副本。

這對我有用:

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

這是一個替代方案,它只是Microsoft 文檔所說內容的直接表示。 它並不短,但與此處的其他解決方案相比,它可以正確處理更多情況。 如果您擔心反射調用的性能,您可以將其包裝在一個簡單的緩存中。

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,這對我來說似乎不太可能,但我不確定。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM