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