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

Sam*_*Sam 15 .net c# assembly x86-64

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

public void SomeMethod(Type messageType)
{
    if (messageType == typeof(BroadcastMessage))
    {
        // ...
    }
    else if (messageType == typeof(DirectMessage))
    {
        // ...
    }
    else if (messageType == typeof(ClientListRequest))
    {
        // ...
    }
}
Run Code Online (Sandbox Code Playgroud)

这是从其他地方的性能关键循环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;
    }
}
Run Code Online (Sandbox Code Playgroud)

在我的机器上的结果(Window 10 x64,.NET 4.7.2,RyuJIT,Release build):

编译到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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

我不知道如何解释ASM所以我无法理解这里发生的事情的重要性.在坚果壳中,为什么F1更快?

Eri*_*ert 12

您发布的程序集显示mjwills的注释正如预期的那样正确.正如链接文章指出的那样,抖动可以很聪明地进行某些比较,而这就是其中之一.

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

mov     rcx,offset mscorlib_ni+0x729e10
Run Code Online (Sandbox Code Playgroud)

rcx是对成员函数的调用的"this指针".在这种情况下,"this pointer"将是某些CLR预分配对象的地址,究竟是什么我不知道.

call    clr!InstallCustomModule+0x2320
Run Code Online (Sandbox Code Playgroud)

现在我们在该对象上调用一些成员函数; 我不知道是什么.你有调试信息的最近的公共函数是InstallCustomModule,但显然我们不是在这里调用InstallCustomModule; 我们正在调用距离InstallCustomModule 0x2320字节的函数.

看看InstallCustomModule + 0x2320的代码是什么意思会很有趣.

无论如何,我们进行调用,返回值以rax为单位.继续:

mov     rcx,qword ptr [rsp+30h]
cmp     qword ptr [rcx+8],rax
Run Code Online (Sandbox Code Playgroud)

这看起来它正在获取aout 的值this并将其与返回的函数进行比较.

其余的代码完全是普通的:将比较的bool结果移动到返回寄存器中.

简而言之,第一个片段相当于:

return ReferenceEquals(SomeConstantObject.SomeUnknownFunction(), this.a);
Run Code Online (Sandbox Code Playgroud)

显然,这里有一个有根据的猜测是常量对象和未知函数是快速获取常用类型对象(如typeof(int))的特殊用途助手.

第二个有根据的猜测是抖动正在决定自己的模式"将类型类型的字段与类型(某物)进行比较"可以最好地作为对象之间的直接参考比较.

现在你可以自己看看第二个片段的作用.它只是:

return Type.op_Equality(this.a, this.b);
Run Code Online (Sandbox Code Playgroud)

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

现在应该清楚为什么第一个片段更快.抖动非常了解第一个片段.例如,它知道typeof(int)将始终返回相同的引用,因此您可以进行廉价的引用比较.它知道typeof(int)永远不会为null.它知道typeof(int)的确切类型 - 记住,Type没有密封; 你可以制作自己的Type物品.

在第二个片段中,抖动只知道它有两个类型的操作数Type.它不知道它们的运行时类型,它不知道它们的无效性; 据他所知,你Type自己进行了子类化并组成了两个引用不相等但价值相等的实例.它必须回到最保守的位置,并调用一个从列表开始的辅助方法:它们都是空的吗?其中一个是null而另一个是非null?他们参考平等吗?等等.

看起来缺乏知识会让你损失......半纳秒的巨额罚款.我不担心.