为什么typeA == typeB比typeA == typeof(TypeB)慢?

时间:2019-02-25 20:45:23

标签: c# .net assembly x86-64

我最近一直在优化/基准测试某些代码,并遇到了这种方法:

public void SomeMethod(Type messageType)
{
    if (messageType == typeof(BroadcastMessage))
    {
        // ...
    }
    else if (messageType == typeof(DirectMessage))
    {
        // ...
    }
    else if (messageType == typeof(ClientListRequest))
    {
        // ...
    }
}

这是在其他地方的性能关键循环中调用的,因此我自然地假设所有这些typeof(...)调用都增加了不必要的开销(我知道是微优化),并且可以移到该类中的私有字段中。 (我知道有更好的方法来重构此代码,但是,我仍然想知道这里发生了什么。)

根据我的基准测试完全不是这种情况(使用BenchmarkDotNet)。

[DisassemblyDiagnoser(printAsm: true, printSource: true)]
[RyuJitX64Job]
public class Tests
{
    private Type a = typeof(string);
    private Type b = typeof(int);

    [Benchmark]
    public bool F1()
    {
        return a == typeof(int);
    }

    [Benchmark]
    public bool F2()
    {
        return a == b;
    }
}

我的机器上的结果(Window 10 x64,.NET 4.7.2,RyuJIT,发行版):

编译为ASM的函数:

F1

mov     rcx,offset mscorlib_ni+0x729e10
call    clr!InstallCustomModule+0x2320
mov     rcx,qword ptr [rsp+30h]
cmp     qword ptr [rcx+8],rax
sete    al
movzx   eax,al

F2

mov     qword ptr [rsp+30h],rcx
mov     rcx,qword ptr [rcx+8]
mov     rdx,qword ptr [rsp+30h]
mov     rdx,qword ptr [rdx+10h]
call    System.Type.op_Equality(System.Type, System.Type)
movzx   eax,al

我不知道如何解释ASM,因此无法理解这里发生的事情的重要性。简而言之,为什么F1更快?

2 个答案:

答案 0 :(得分:12)

您发布的程序集显示,mjwills的注释符合预期,是正确的。如链接文章所述,对于某些比较而言,抖动可能很聪明,这就是其中之一。

让我们看看您的第一个片段:

mov     rcx,offset mscorlib_ni+0x729e10

rcx是成员函数调用的“ this指针”。在这种情况下,“ this指针”将是一些CLR预分配对象的地址,我不知道到底是什么。

call    clr!InstallCustomModule+0x2320

现在我们在该对象上调用一些成员函数;我不知道您具有调试信息的 nearest 公共功能是InstallCustomModule,但很明显,我们在这里不调用InstallCustomModule。我们正在调用距离InstallCustomModule 0x2320字节的函数。

看看InstallCustomModule + 0x2320上的代码的作用会很有趣。

无论如何,我们进行了调用,返回值以rax开头。继续:

mov     rcx,qword ptr [rsp+30h]
cmp     qword ptr [rcx+8],rax

这似乎是从a中提取this的值,并将其与返回的函数进行比较。

其余的代码非常普通:将比较的布尔结果移到返回寄存器中。

简而言之,第一个片段等效于:

return ReferenceEquals(SomeConstantObject.SomeUnknownFunction(), this.a);

显然,这里的一个有根据的猜测是,常量对象和未知函数是专用帮助程序,可以快速获取诸如typeof(int)之类的常用类型对象。

第二种有根据的猜测是,抖动决定自己最好将模式“将类型Type的字段与typeof(某物)进行比较”作为对象之间的直接引用比较。

现在您可以亲眼看到第二个片段的作用。只是:

return Type.op_Equality(this.a, this.b);

它所做的只是调用一个辅助方法,该方法比较两种类型的值是否相等。请记住, CLR不能保证所有等效类型对象的引用相等

现在应该清楚为什么第一个片段更快。 抖动对第一个片段了解得更多。例如,它知道typeof(int)将始终返回相同的引用,因此您可以进行廉价的引用比较。它知道typeof(int)永远不会为null。它知道typeof(int)的 exact 类型-记住,Type不是密封的;您可以创建自己的Type对象。

在第二个片段中,抖动除了具有两个Type类型的操作数之外,什么都不知道。它不知道它们的运行时类型,也不知道它们的无效性。就其所知,您自己对Type进行了子类化,并组成了两个引用不相等但值相等的实例。它必须退回到最保守的位置,并调用一个开始在列表中的辅助方法:它们都为null吗?是空值之一,另一个是非空值吗?他们参考相等吗?依此类推。

似乎缺乏知识使您付出了……半秒的巨大代价。我不用担心。

答案 1 :(得分:0)

如果您好奇,还可以查看jit使用的逻辑,请参见gtFoldTypeCompare

Jit可以做很多事情来简化甚至消除类型比较。他们都需要对所比较类型的创建有所了解。