Is there realy significant performance advantage of using typeof(String)
vs System.Type.GetType("System.String")
?
If there is, I'd like to know why. Go as deep into the CLR as you have to to prove it.
My tests show yes, by a good margin.
Results
Configuration=Release
baseline: 5572 ticks 2 ms
typeof(Test): 8757 ticks 3 ms
Type.GetType(String): 3899966 ticks 1482 ms
Code
[MethodImpl(MethodImplOptions.NoInlining)]
static int Call(Type t)
{
return 1;
}
static void Main(string[] args)
{
const int Iterations = 1000000;
int count;
Stopwatch sw = Stopwatch.StartNew(); count = 0;
for (int i = 0; i < Iterations; i++)
{
count += Call(null);
}
sw.Stop();
Console.WriteLine("baseline: {0} ticks {1} ms", sw.ElapsedTicks, sw.ElapsedMilliseconds);
sw = Stopwatch.StartNew(); count = 0;
for (int i = 0; i < Iterations; i++)
{
count += Call(typeof(String));
}
sw.Stop();
Console.WriteLine("typeof(Test): {0} ticks {1} ms", sw.ElapsedTicks, sw.ElapsedMilliseconds);
sw = Stopwatch.StartNew(); count = 0;
for (int i = 0; i < Iterations; i++)
{
count += Call(Type.GetType("System.String"));
}
sw.Stop();
Console.WriteLine("Type.GetType(String): {0} ticks {1} ms", sw.ElapsedTicks, sw.ElapsedMilliseconds);
}
Results
Configuration=Debug
typeof(Test): 24782 ticks 9 ms
Type.GetType(String): 4783195 ticks 1818 ms
Code
static void Main()
{
const int Iterations = 1000000;
Stopwatch sw = Stopwatch.StartNew();
for (int i = 0; i < Iterations; i++)
{
Type t = typeof(String);
}
sw.Stop();
Console.WriteLine("typeof(Test): {0} ticks {1} ms", sw.ElapsedTicks, sw.ElapsedMilliseconds);
sw = Stopwatch.StartNew();
for (int i = 0; i < Iterations; i++)
{
Type t = System.Type.GetType("System.String");
}
sw.Stop();
Console.WriteLine("Type.GetType(String): {0} ticks {1} ms", sw.ElapsedTicks, sw.ElapsedMilliseconds);
}
You kind of answered your own question. typeof(string)
is faster. But it's interesting to see why.
typeof
is compiled to ldtoken
and GetTypeFromHandle
(see Efficiency of C#'s typeof operator (or whatever its representation is in MSIL)). This is more efficient than GetType("System.String")
.
Also note that the benchmarks in Version 1 are invalid because the result variable Type t
is not used. Not using the local variable will cause the JIT to optimize the statement away. The first loop body would effectively be a no-operation, but the second loop will execute. This is my guess based on the performance numbers you reported.
Here's a benchmark done right. The NoInline
function serves as a sink to the value you want to benchmark. Disadvantage is that you're now benchmarking function call costs but they are small.
Because it's doing more work @JJS. Remember your training and use the source, Luke.
The documentation gives us some clues. Type.GetType Method (String)
we know that typeof(T)
is a call to is compiled to ldtoken
and GetTypeFromHandle
, but what is GetTypeFromHandle
doing compared to GetTypeByName
?
Lets solve the easy one first.
GetTypeFromHandle
is defined as
[Pure]
[System.Security.SecuritySafeCritical] // auto-generated
[ResourceExposure(ResourceScope.None)]
[MethodImpl(MethodImplOptions.InternalCall)]
public static extern Type GetTypeFromHandle(RuntimeTypeHandle handle);
Lets get a version of the CLR we can reference.
Shared Source Common Language Infrastructure 2.0 Release
runtimehandles.cpp
FCIMPL1(Object*, RuntimeTypeHandle::GetRuntimeType, void* th) {
CONTRACTL {
THROWS;
DISABLED(GC_TRIGGERS);
MODE_COOPERATIVE;
SO_TOLERANT;
}
CONTRACTL_END;
OBJECTREF refType = NULL;
TypeHandle typeHandle = TypeHandle::FromPtr(th);
TypeHandle* pTypeHandle = &typeHandle;
_ASSERTE(CheckPointer(pTypeHandle));
_ASSERTE(CheckPointer(pTypeHandle->AsPtr(), NULL_OK));
if (pTypeHandle->AsPtr() == NULL)
return NULL;
refType = pTypeHandle->GetManagedClassObjectIfExists();
if (refType != NULL)
return OBJECTREFToObject(refType);
HELPER_METHOD_FRAME_BEGIN_RET_ATTRIB_1(Frame::FRAME_ATTR_RETURNOBJ, refType);
refType = pTypeHandle->GetManagedClassObject();
HELPER_METHOD_FRAME_END();
return OBJECTREFToObject(refType);
}
FCIMPLEND
Alright. This is legit. We are doing a wicked simple call here to get an OBJECTREFToObject.
No searching, just looking up a type effectively by it's method table. Need a refresher on .Net internals?
OK, what about the slow method? Type.GetType Method (String)
Chase the call stack and find out that it calls RuntimeTypeHandle.GetTypeByName
[System.Security.SecurityCritical] // auto-generated
[ResourceExposure(ResourceScope.None)]
[DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)]
[SuppressUnmanagedCodeSecurity]
private extern static void GetTypeByName(string name, bool throwOnError, bool ignoreCase, bool reflectionOnly, StackCrawlMarkHandle stackMark,
#if FEATURE_HOSTED_BINDER
IntPtr pPrivHostBinder,
#endif
bool loadTypeFromPartialName, ObjectHandleOnStack type);
runtimehandles.cpp
FCIMPL6(EnregisteredTypeHandle, RuntimeTypeHandle::GetTypeByName,
StringObject* classNameUNSAFE, CLR_BOOL bThrowOnError, CLR_BOOL bIgnoreCase, CLR_BOOL bReflectionOnly, StackCrawlMark* pStackMark, CLR_BOOL bLoadTypeFromPartialNameHack)
{
CONTRACTL
{
THROWS;
DISABLED(GC_TRIGGERS);
MODE_COOPERATIVE;
SO_TOLERANT;
}
CONTRACTL_END;
STRINGREF sRef = (STRINGREF) classNameUNSAFE;
TypeHandle typeHandle;
HELPER_METHOD_FRAME_BEGIN_RET_1(sRef);
{
if (!sRef)
COMPlusThrowArgumentNull(L"className",L"ArgumentNull_String");
typeHandle = TypeName::GetTypeManaged(sRef->GetBuffer(), NULL, bThrowOnError, bIgnoreCase, bReflectionOnly, /*bProhibitAsmQualifiedName =*/ FALSE, pStackMark, bLoadTypeFromPartialNameHack);
}
HELPER_METHOD_FRAME_END();
return typeHandle.AsPtr();
}
FCIMPLEND
Fine, but what is TypeName::GetTypeManaged doing?!
typeparse.cpp
//--------------------------------------------------------------------------------------------------------------
// This everything-but-the-kitchen-sink version is what used to be called "GetType()". It exposes all the
// funky knobs needed for implementing the specific requirements of the managed Type.GetType() apis and friends.
//--------------------------------------------------------------------------------------------------------------
/*public static */ TypeHandle TypeName::GetTypeManaged
But it doesn't stop there
typeparse.cpp
// -------------------------------------------------------------------------------------------------------------
// This is the "uber" GetType() that all public GetType() funnels through. It's main job is to figure out which
// Assembly to load the type from and then invoke GetTypeHaveAssembly.
//
// It's got a highly baroque interface partly for historical reasons and partly because it's the uber-function
// for all of the possible GetTypes.
// -------------------------------------------------------------------------------------------------------------
/* private instance */ TypeHandle TypeName::GetTypeWorker
Doesn't stop here either.
typeparse.cpp
//----------------------------------------------------------------------------------------------------------------
// This is the one that actually loads the type once we've pinned down the Assembly it's in.
//----------------------------------------------------------------------------------------------------------------
/* private instance */ TypeHandle TypeName::GetTypeHaveAssembly(Assembly* pAssembly, BOOL bThrowIfNotFound, BOOL bIgnoreCase, BOOL bRecurse)
for (COUNT_T i = 0; i < names.GetCount(); i ++)
{
LPCWSTR wname = names[i]->GetUnicode();
MAKE_UTF8PTR_FROMWIDE(name, wname);
typeName.SetName(name);
th = pAssembly->GetLoader()->LoadTypeHandleThrowing(&typeName);
}
clsload.cpp
TypeHandle ClassLoader::LoadTypeHandleThrowing(NameHandle* pName, ClassLoadLevel level, Module* pLookInThisModuleOnly/*=NULL*/)
BOOL foundSomething = FindClassModuleThrowing(pName,
// FindClassModuleThrowing discovers which module the type you're looking for is in and loads the Module if necessary. // Basically, it iterates through all of the assembly's modules until a name match is found in a module's // AvailableClassHashTable.
if (!typeHnd.IsNull()) {
typeHnd = LoadTypeDefThrowing(typeHnd.GetModule(), typeHnd.GetCl(),
// Given a token specifying a typeDef, and a module in which to // interpret that token, find or load the corresponding type handle.
typeHnd = pModule->LookupTypeDef(typeDef, level);
ceeload.h
TypeHandle LookupTypeDef(mdTypeDef token, ClassLoadLevel level = CLASS_LOAD_UNRESTOREDTYPEKEY)
which is giving us the TypeHandle. The same thing we got in a single stack frame of GetTypeFromHandle
PTR_MethodTable pMT = PTR_MethodTable(GetFromRidMap(&m_TypeDefToMethodTableMap, RidFromToken(token)));
if (pMT == NULL || pMT->GetLoadLevel() < level)
return TypeHandle();
else
return (TypeHandle)pMT;
So... What's slow? The iteration in FindClassModuleThrowing. It has to iterate through names to lookup the Method Table... Iterating over an array, is always slower than looking up something by a known key, which was available in GetTypeFromHandle
Case closed.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With