Performance of typeof(String) vs System.Type.GetType("S
本文关键字:GetType quot Type vs typeof of String Performance System | 更新日期: 2023-09-27 18:36:13
使用 typeof(String)
与 System.Type.GetType("System.String")
相比,真的有显著的性能优势吗?
如果有,我想知道为什么。尽可能深入地了解 CLR 以证明这一点。
我的测试显示是的,有很大的优势。
版本 2
结果
配置 = 发布
baseline: 5572 ticks 2 ms
typeof(Test): 8757 ticks 3 ms
Type.GetType(String): 3899966 ticks 1482 ms
法典
[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);
}
版本 1
结果
配置 = 调试
typeof(Test): 24782 ticks 9 ms
Type.GetType(String): 4783195 ticks 1818 ms
法典
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);
}
你有点回答了你自己的问题。 typeof(string)
更快。但有趣的是,看看为什么。
typeof
被编译为ldtoken
和GetTypeFromHandle
(请参阅 C# 的类型运算符的效率(或其在 MSIL 中的任何表示形式))。这比GetType("System.String")
更有效。
另请注意,版本 1 中的基准无效,因为未使用结果变量 Type t
。不使用局部变量将导致 JIT 优化语句。第一个循环体实际上是一个无操作,但第二个循环将执行。这是我根据您报告的性能数字进行的猜测。
这是一个正确的基准测试。NoInline
函数用作要进行基准测试的值的接收器。缺点是你现在正在对函数调用成本进行基准测试,但它们很小。
因为它@JJS做更多的工作。记住你的训练并使用资源,卢克。
文档为我们提供了一些线索。Type.GetType Method (String)
- 如果某个类型在编译时位于程序已知的程序集中,则在 C#、Visual Basic 中的 GetType 或 C++ 中使用会更有效。
- 如果 typeName 包含命名空间,但不包含程序集名称,则此方法仅按该顺序搜索调用对象的程序集和 Mscorlib.dll。
我们知道typeof(T)
是一个调用被编译为ldtoken
和GetTypeFromHandle
,但与GetTypeByName
相比,GetTypeFromHandle
在做什么?
让我们先解决简单的问题。 GetTypeFromHandle
定义为
[Pure]
[System.Security.SecuritySafeCritical] // auto-generated
[ResourceExposure(ResourceScope.None)]
[MethodImpl(MethodImplOptions.InternalCall)]
public static extern Type GetTypeFromHandle(RuntimeTypeHandle handle);
让我们获取一个可以引用的 CLR 版本。
共享源公共语言基础结构 2.0 发布
运行时句柄.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
好。这是合法的。我们在这里做一个邪恶的简单调用来获取一个 OBJECTREFToObject。
没有搜索,只是通过它的方法表有效地查找类型。需要复习一下 .Net 内部结构?
好的,慢方法呢?Type.GetType Method (String)
追逐调用堆栈并发现它调用 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);
运行时句柄.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
很好,但是TypeName::GetTypeManage在做什么?!
类型解析.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
但它并不止于此
类型解析.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
也不止于此。
类型解析.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);
}
负载.cpp
TypeHandle ClassLoader::LoadTypeHandleThrowing(NameHandle* pName, ClassLoadLevel level, Module* pLookInThisModuleOnly/*=NULL*/)
BOOL foundSomething = FindClassModuleThrowing(pName,
FindClassModuleThrowing 会发现您要查找的类型在哪个模块中,并在必要时加载该模块。基本上,它会遍历程序集的所有模块,直到在模块的可用类哈希表。
if (!typeHnd.IsNull()) {
typeHnd = LoadTypeDefThrowing(typeHnd.GetModule(), typeHnd.GetCl(),
给定一个指定 typeDef 的标记,以及一个模块解释该令牌,查找或加载相应的类型句柄。
typeHnd = pModule->LookupTypeDef(typeDef, level);
Ceeload.h
TypeHandle LookupTypeDef(mdTypeDef token, ClassLoadLevel level = CLASS_LOAD_UNRESTOREDTYPEKEY)
这给了我们TypeHandle。我们在单个堆栈帧中得到的相同的东西GetTypeFromHandle
PTR_MethodTable pMT = PTR_MethodTable(GetFromRidMap(&m_TypeDefToMethodTableMap, RidFromToken(token)));
if (pMT == NULL || pMT->GetLoadLevel() < level)
return TypeHandle();
else
return (TypeHandle)pMT;
所以。。。什么慢?FindClassModuleThrowing中的迭代。它必须遍历名称才能查找方法表...遍历数组总是比通过已知键查找某些内容慢,已知键在 GetTypeFromHandle
中可用
案件已结案。