首页 > 解决方案 > 为什么 typeA == typeB 比 typeA == typeof(TypeB) 慢?

问题描述

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

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、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

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 更快?

标签: c#.netassemblyx86-64

解决方案


您发布的程序集显示 mjwills 的评论正如预期的那样是正确的。正如链接的文章所指出的,抖动对于某些比较可能很聪明,这就是其中之一。

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

mov     rcx,offset mscorlib_ni+0x729e10

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

call    clr!InstallCustomModule+0x2320

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

看看 InstallCustomModule+0x2320 的代码做了什么会很有趣。

无论如何,我们进行调用,返回值进入 rax。继续:

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

看起来它正在获取aout of的值this并将其与函数返回的任何内容进行比较。

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

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

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

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

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

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

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

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

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

在第二个片段中,jitter 只知道它有两个类型为 的操作数Type。它不知道它们的运行时类型,也不知道它们的无效性;据它所知,您对Type自己进行了子类化并组成了两个引用不相等但值相等的实例。它必须回退到最保守的位置并调用一个从列表开始向下的辅助方法:它们都是空的吗?一个是空的,另一个是非空的吗?它们的引用是否相等?等等。

看起来缺乏知识会让你付出巨大的代价……半纳秒。我不会担心的。


推荐阅读