【问题标题】:Performance of typeof(String) vs System.Type.GetType("System.String")typeof(String) 与 System.Type.GetType("System.String") 的性能
【发布时间】:2016-02-24 19:30:11
【问题描述】:

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

【问题讨论】:

  • 两个基准都是无效的,因为结果被丢弃了。 JIT 可能会对此进行优化。
  • typeof(string) 将在编译时处理。 Type.GetType("System.String") 要求在运行时解决它。当然会更快。
  • 希望@jonskeet 几年前能在他的回答中包含这个。 stackoverflow.com/a/353435/26877
  • 这是一个毫无意义的问题。您不需要多次调用该函数,因为答案不会改变。
  • 版本 1 中的第一个循环是空的,因为 JIT 丢弃了 Type t = typeof(String); 行。

标签: c# .net


【解决方案1】:

你有点回答了你自己的问题。 typeof(string) 更快。但有趣的是看看为什么。

typeof 编译为ldtoken 和GetTypeFromHandle(请参阅Efficiency of C#'s typeof operator (or whatever its representation is in MSIL))。这比GetType("System.String") 更有效率。

另请注意,版本 1 中的基准测试无效,因为未使用结果变量 Type t。不使用局部变量将导致 JIT 优化语句。第一个循环体实际上是无操作的,但第二个循环将执行。这是我根据您报告的性能数据的猜测。

Here's a benchmark done right. NoInline 函数用作您想要进行基准测试的值的接收器。缺点是您现在正在对函数调用成本进行基准测试,但它们很小。

【讨论】:

  • 我在 Configuration=Debug 中明确做了测试以避免优化器。我对帖子进行了编辑,以包含 JonSkeet 的技术,以及用于比较的基线。
  • 那就更糟了。调试模式下的性能与优化下的性能非常无关。新基准对我来说似乎有效。在没有附加调试器的情况下在发布模式下运行它。由于这是一个常见的错误,请明确确认您在没有附加调试器的情况下运行它 (Ctrl-F5)。
  • 我不知道为什么 Jons 基准测试的 typeof 比你的慢。也许是在过去 7 年中添加的运行时优化。
  • 我在 %path% 中使用带有 msbuild 的控制台运行了发布代码。没有 Visual Studio 打开按 ctrl+F5 在...
  • 好的。每秒 1 亿次类型的评估对我来说似乎很合适。这个数字是可信的。每个 typeof 有 30-120 条指令。这不仅仅是一次循环迭代和一次调用,这意味着它们都没有被优化。你的问题解决了吗?我认为基准和结论是有效的(9ms 太短,无法获得准确的数字,但很明显一个版本比另一个更好)。
【解决方案2】:

因为它正在做更多的工作@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 版本。

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

好的。这是合法的。我们在这里做了一个邪恶的简单调用来获取一个 OBJECTREFToObject。

没有搜索,只是通过它的方法表有效地查找一个类型。需要复习.Net internals?

好的,slow 方法呢? 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);

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

很好,但是 TypeName::GetTypeManaged 在做什么?!

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

但不止于此

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

也不止于此。

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 发现您要查找的类型在哪个模块中,并在必要时加载该模块。 // 基本上,它遍历程序集的所有模块,直到在模块的 // 可用类哈希表。

    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中可用@

结案。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-07-10
    • 1970-01-01
    • 2011-04-15
    • 1970-01-01
    • 2018-10-13
    • 1970-01-01
    • 1970-01-01
    • 2016-12-18
    相关资源
    最近更新 更多