在我的serialiser / deserialiser中,我有以下代码段:
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的最快方法是什么?
答案 0 :(得分: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));
}
作为副作用,这会为false
返回(尽管正确)string
,因为GetUninitializedObject
无法创建它。假设Alloc
确实检查了盲目性(string
除外),这应该是可靠的。
答案 1 :(得分: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 { }
}
}
答案 2 :(得分:2)
@IllidanS4在此页面上出色的代码错误地返回false
数组,其中元素是blittable formatted type,这意味着数组也是blittable。从这个例子开始,我修复了这个问题并添加了一些处理不当的案例,例如:
T[]
其中T
:formatted-type(刚才提及)int[][][]...
System.Enum
ittself)我还补充说,避免昂贵的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;
}
其他答案的漂亮缓存机制应该按原样使用。
答案 3 :(得分:1)
我没有足够的声誉来添加评论,所以我会写下我的评论作为答案:
我测试了@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));
}
}
答案 4 :(得分:0)
使用http://msdn.microsoft.com/en-us/library/system.type.islayoutsequential.aspx和http://msdn.microsoft.com/en-us/library/system.type.isexplicitlayout.aspx:
element_type.IsValueType && collection_type.IsArray && (element_type.IsLayoutSequential || element_type.IsExplicitLayout)
答案 5 :(得分:0)
最快的方法是不分配而是重新使用现有的GCHandle,例如:
var gch = GCHandle.Alloc(null, GCHandleType.Pinned);
gch.Target = new byte[0];
gch.Target = "";
GCHandle.Alloc
每次使用锁分配或重用现有插槽,这是相对昂贵的操作。静态启动时,静态只读基元类型变为常量,但不将GCHandle存储为通用类型,因为每个通用实例化都将拥有自己的副本。
答案 6 :(得分:0)
以 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 类型。 Reference
答案 7 :(得分:0)
这对我有用:
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;
}
答案 8 :(得分:0)
这里有一个替代方案,它只是对 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,这在我看来不太可能,但我不确定。