我最近一直在优化/基准测试某些代码,并遇到了这种方法:
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更快?
答案 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可以做很多事情来简化甚至消除类型比较。他们都需要对所比较类型的创建有所了解。