问题
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[]
whereT
: 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