代码之家  ›  专栏  ›  技术社区  ›  Sam Chad

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

  •  2
  • Sam Chad  · 技术社区  · 7 年前

    我最近对一些代码进行了优化/基准测试,发现了以下方法:

    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;
        }
    }
    

    在我的机器上的结果(Windows 10 x64、.NET 4.7.2、RyuJIT、发布版本):

    编译为ASM的函数:

    一层楼

    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
    

    地上二层

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

    0 回复  |  直到 7 年前
        1
  •  13
  •   mjwills Myles McDonnell    7 年前

    你发布的组件显示mjwills的评论正如预期的那样是正确的。正如链接文章所指出的,抖动在某些比较中可能是聪明的,这就是其中之一。

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

    mov     rcx,offset mscorlib_ni+0x729e10
    

    rcx是成员函数调用的“此指针”。本例中的“this pointer”将是某个CLR预分配对象的地址,具体我不知道。

    call    clr!InstallCustomModule+0x2320
    

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

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

    不管怎样,我们进行调用,返回值以rax表示。接下来:

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

    这看起来像是在获取 a 从…里面 this 并将其与函数返回的内容进行比较。

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

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

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

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

    第二个有根据的猜测是,抖动本身决定了模式“将类型字段与(某物)类型进行比较”可以最好地作为对象之间的直接参考比较。

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

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

    它所做的只是调用一个助手方法来比较两种类型的值是否相等。回想起 CLR不能保证所有等效类型对象的引用相等 .

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

    在第二个片段中,抖动只知道它有两个类型的操作数 类型 .它不知道它们的运行时类型,也不知道它们的空值;据它所知,你属于 类型 你和你自己组成了两个引用不相等但价值相等的实例。它必须退回到最保守的位置,并调用一个助手方法,该方法开始进入列表:它们都为null吗?其中一个为空,另一个为非空?他们是平等的吗?等等

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

        2
  •  2
  •   Andy Ayers    7 年前

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

    jit可以做很多事情来简化甚至消除类型比较。它们都需要了解被比较类型的创建。