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

Is there a significant performance advantage when using typeof(String) vs System.Type.GetType("System.String") ?

If there is, I would like to know why. Go as deep into the CLR as you have to prove it.

My tests show, yes, with good returns.

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 = Debugging

 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); } 
+6
source share
2 answers

You answered your question. typeof(string) faster. But itโ€™s interesting to know why.

typeof compiled into ldtoken and GetTypeFromHandle (see C # Type Operator Efficiency (or whatever its representation in MSIL ). It is more efficient than GetType("System.String") .

Also note that the tests in Version 1 are invalid because the Type t result variable is not used. Without using a local variable, JIT optimizes the statement. The first body of the cycle will work efficiently without an operation, but the second cycle will be executed. This is my guess based on the performance metrics you reported.

Here is the correct result. The NoInline function serves as a receiver for the value you want to check. The downside is that you are now comparing the cost of function calls, but they are small.

+8
source

Because he does more work @JJS. Remember your training and use the source, Luke.

The documentation gives some tips. Type.GetType (String) Method

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

we know that typeof(T) is a call that compiled into ldtoken and GetTypeFromHandle , but what does GetTypeFromHandle do compared to GetTypeByName ?

First let's resolve the simple one. GetTypeFromHandle defined as

 [Pure] [System.Security.SecuritySafeCritical] // auto-generated [ResourceExposure(ResourceScope.None)] [MethodImpl(MethodImplOptions.InternalCall)] public static extern Type GetTypeFromHandle(RuntimeTypeHandle handle); 

Lets get the CLR version that we can reference.

Common Version 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 

Good. It is legal. We make an evil simple call here to get an OBJECTREFToObject.

No search, just looking at the type efficiently using the method table. Need to upgrade .NET internals ?

OK, what about the slow method? Type.GetType (String) Method

Change the call stack and find out that it is calling 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 

Ok, but what is TypeName :: GetTypeManaged to do ?!

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 does not stop there

typeparse.cpp

 // ------------------------------------------------------------------------------------------------------------- // This is the "uber" GetType() that all public GetType() funnels through. It main job is to figure out which // Assembly to load the type from and then invoke GetTypeHaveAssembly. // // It got a highly baroque interface partly for historical reasons and partly because it the uber-function // for all of the possible GetTypes. // ------------------------------------------------------------------------------------------------------------- /* private instance */ TypeHandle TypeName::GetTypeWorker 

It does not stop here.

typeparse.cpp

 //---------------------------------------------------------------------------------------------------------------- // This is the one that actually loads the type once we've pinned down the Assembly it 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 detects which module the type you are looking for is in, and loads the module if necessary. // Basically, it iterates through all the assembly modules until a name match is found in the module // AvailableClassHashTable.

  if (!typeHnd.IsNull()) { typeHnd = LoadTypeDefThrowing(typeHnd.GetModule(), typeHnd.GetCl(), 

// Given the marker indicating the type of Def and the module in which // we interpret this token, we find or load the corresponding type descriptor.

 typeHnd = pModule->LookupTypeDef(typeDef, level); 

ceeload.h

 TypeHandle LookupTypeDef(mdTypeDef token, ClassLoadLevel level = CLASS_LOAD_UNRESTOREDTYPEKEY) 

which gives us TypeHandle. We got the same thing in the same GetTypeFromHandle frame stack.

  PTR_MethodTable pMT = PTR_MethodTable(GetFromRidMap(&m_TypeDefToMethodTableMap, RidFromToken(token))); if (pMT == NULL || pMT->GetLoadLevel() < level) return TypeHandle(); else return (TypeHandle)pMT; 

So ... what is slow? Iterate over FindClassModuleTrowing. It must iterate over the names to find the method table ... Iterating over an array is always slower than finding something with the well-known key available in GetTypeFromHandle

Case is closed.

+3
source

Source: https://habr.com/ru/post/1243792/


All Articles