Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Performance of typeof(String) vs System.Type.GetType("System.String")

Tags:

c#

.net

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.

Version 2

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

Version 1

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);
}
like image 514
JJS Avatar asked Feb 24 '16 19:02

JJS


2 Answers

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.

like image 171
usr Avatar answered Nov 11 '22 10:11

usr


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)

  • If a type is in an assembly known to your program at compile time, it is more efficient to use in C#, GetType in Visual Basic, or in C++.
  • If typeName includes the namespace but not the assembly name, this method searches only the calling object's assembly and Mscorlib.dll, in that order.

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.

like image 29
JJS Avatar answered Nov 11 '22 11:11

JJS