简体   繁体   English

参考平等性能差异? ((object)obj1 ==(object)obj2)vs。object.ReferenceEquals(obj1,obj2)

[英]Reference equality performance difference? ((object)obj1 == (object)obj2) vs. object.ReferenceEquals( obj1, obj2 )

Is there extra overhead in using the object.ReferenceEquals method verses using ((object)obj1 == (object)obj2) ? 使用object.ReferenceEquals方法是否有额外的开销((object)obj1 == (object)obj2)

In the first case, there would be a static method call involved, and in both cases some form of casting to an object would be involved. 在第一种情况下,将涉及静态方法调用,并且在两种情况下都涉及到对象的某种形式的转换。

Even if the compiler balances out those methods, what about inequality? 即使编译器平衡了那些方法,那么不等式呢?

(object)obj != null

as compared to... 相比于...

!object.ReferenceEquals(obj,null)

I suppose that at some point, a logical negation would occur, either within the != operator, or as applied to the result of the ReferenceEquals method. 我想在某些时候,会在!=运算符内或者应用于ReferenceEquals方法的结果时发生逻辑否定。 What do you think? 你怎么看?

There's also the issue of readability to consider. 还有可读性问题需要考虑。 ReferenceEquals seems clearer when checking equality, but for inequality, one might miss the ! 在检查相等性时,ReferenceEquals似乎更清晰,但对于不平等,可能会错过! preceding object.ReferenceEquals , whereas the != in the first variation is hard to overlook. object.ReferenceEquals之前,而第一个变体中的!=很难被忽视。

Is there extra overhead in using the object.ReferenceEquals method 使用object.ReferenceEquals方法是否有额外的开销

No. The method directly contains the minimal IL description to perform the reference equality check (for the record: it's equivalent to VB's Is operator) and will often be inlined by the JIT (especially when targeting x64) so there's no overhead. 不。该方法直接包含执行引用相等性检查的最小IL描述(对于记录:它相当于VB的Is运算符),并且通常由JIT内联(特别是在定位x64时),因此没有开销。

About readability: I personally think that object.ReferenceEquals is potentially more readable (even in the negated form) because it explicitly expresses its semantics. 关于可读性:我个人认为object.ReferenceEquals可能更具可读性(即使是否定形式)因为它明确表达了它的语义。 The cast to object may be confusing to some programmers. 强制转换为object可能会让一些程序员感到困惑。

I've just found an article discussing this. 我刚刚发现了一篇讨论此事的文章 It prefers (object)x == y because the IL footprint is smaller. 它更喜欢(object)x == y因为IL足迹较小。 It argues that this might facilitate inlining of method X using this comparison. 它认为这可能有助于使用这种比较来内联方法X However (without any detailed knowledge of the JIT but logically and intuitively) I believe this is wrong: if the JIT behaves anything like an optimizing C++ compiler, it will consider the method after inlining the call to ReferenceEquals , so (for the sake of inlining method X ) the memory footprint will be exactly the same either way. 但是(没有任何关于JIT的详细知识,但在逻辑上和直观上)我认为这是错误的:如果JIT表现得像优化C ++编译器,它会在内联对ReferenceEquals的调用之后考虑该方法,所以(为了内联)方法X )内存占用将完全相同。

That is to say: choosing one way over the other will have no impact whatsoever on the JIT and consequently on performance. 也就是说:选择一种方式而不是另一种方式对JIT没有任何影响,从而对性能没有任何影响。

Contrary to answers here, I found (object) == faster than object.ReferenceEquals . 与此处的答案相反,我发现(object) ==object.ReferenceEquals更快。 As to how faster, very negligible! 至于如何更快,非常微不足道!

Test bed: 试验台:

I know you need reference equality check, but I'm including static object.Equals(,) method as well in case of classes where its not overriden. 我知道你需要引用相等性检查,但我还包括静态object.Equals(,)方法,以防它的类未被覆盖。

Platform: x86; 平台:x86; Configuration: Release build 配置:发布版本

class Person {
}

public static void Benchmark(Action method, int iterations = 10000)
{
    Stopwatch sw = new Stopwatch();
    sw.Start();
    for (int i = 0; i < iterations; i++)
        method();

    sw.Stop();
    MsgBox.ShowDialog(sw.Elapsed.TotalMilliseconds.ToString());
}

Test: 测试:

Person p1 = new Person();
Person p2 = new Person();
bool b;
Benchmark(() =>
{
    b = (object)p1 == (object)p2; //960 ~ 1000ms
    b = object.ReferenceEquals(p1, p2); //~ 1250ms
    b = object.Equals(p1, p2); //2100ms
    b = EqualityComparer<Person>.Default.Equals(p1, p2); //~4000ms

}, 100000000);

Person p1 = new Person();
Person p2 = null;
bool b;
Benchmark(() =>
{
    b = (object)p1 == (object)p2; //990 ~ 1000ms
    b = object.ReferenceEquals(p1, p2); // 1230 ~ 1260ms
    b = object.Equals(p1, p2); //1250 ~ 1300ms
    b = EqualityComparer<Person>.Default.Equals(p1, p2); //~3100ms

}, 100000000);

Person p1 = null;
Person p2 = null;
bool b;
Benchmark(() =>
{
    b = (object)p1 == (object)p2; //960 ~ 1000ms
    b = object.ReferenceEquals(p1, p2); //1260 ~ 1270ms
    b = object.Equals(p1, p2); //1180 ~ 1220ms
    b = EqualityComparer<Person>.Default.Equals(p1, p2); //~3100ms

}, 100000000);

Person p1 = new Person();
Person p2 = p1;
bool b;
Benchmark(() =>
{
    b = (object)p1 == (object)p2; //960 ~ 1000ms
    b = object.ReferenceEquals(p1, p2); //1260 ~ 1280ms
    b = object.Equals(p1, p2); //1150 ~ 1200ms
    b = EqualityComparer<Person>.Default.Equals(p1, p2); //3700 ~ 3800ms

}, 100000000);

object.Equals(,) calls ReferenceEquals internally and if they are not equal it would call the overriden virtual Equals method of the class, and hence you see the notice the speed difference. object.Equals(,)内部调用ReferenceEquals ,如果它们不相等,它将调用类的重写虚拟Equals方法,因此您会看到速度差异的通知。

Results were consistent in Debug configuration too... 结果在Debug配置中也是一致的......

As pointed out, emphasis should be on Readability/meaningfulness/revealing intent. 正如所指出的,重点应放在可读性/意义/揭示意图上。

Adding my two cents, after many late hours in perf critical code, on very large codebases with sometimes crazy deep call depths. 经过很长时间的关键代码后,在非常大的代码库中添加了我的两分钱,有时候是疯狂的深度调用深度。

Outside a 'micro benchmark' in the real world, the JIT has many more problems and concerns, and niether has the luxry of the compile time C++ WPO, nor the ease of the C# compilers more direct translations, and yet all the problems of not nessesarily having all the context after C# compiler is done. 在现实世界中的“微观基准”之外,JIT还有更多的问题和关注点,而且它还有编译时间C ++ WPO的简洁性,也没有C#编译器更容易直接翻译,而且所有问题都没有。在C#编译器完成之后,确实拥有了所有上下文。

The 'pedantic' forms: '迂腐'形式:

if ((object)a == (object)b) { }     // ref equals

if (!((object)a == (object)b)) { }  // ref not equals

If you really have honest-to-god perf issues weighed and mesured, or need to take pressure off the JIT for a few really big pervasive classes, this can help a ton. 如果你真的有诚实到上帝的性能问题权衡和测量,或者需要从JIT中为一些非常普遍的类别施加压力,这可以帮助大量。 Same is true with NullOrEmpty vs '(object)str == null || NullOrEmpty vs'(object)str == null ||也是如此 str.Length == 0' . str.Length == 0'。

Not having the luxury of WPO, and all the contraints of not knowing in many cases, what assemblies may get loaded or unloaded after its taken a whack at JITing, odd non-deterministic things happen with respect to what gets optimized and how. 没有WPO的奢侈品,以及在许多情况下都不知道的所有约束,在JITing遭遇重击之后可能会加载或卸载哪些程序集,奇怪的非确定性事情会发生在优化的方面以及如何进行。

This is a huge topic, but here are a few points: 这是一个很大的话题,但这里有几点:

  1. The JIT will chase inlining and register optimzation a down call depth only so far , and totally depends on what else is going on at the time. 到目前为止JIT将追逐内联和优化注册优化 ,并且完全取决于当时正在发生的其他事情。 If you end up compiling a function up the chain one time due to use, and one farther down the chain a different run, you can get drastically differnet results. 如果由于使用而最终在链上编译一次函数,并且在链的另一端进行不同的运行,则可以获得截然不同的结果。 The worst thing for many applications that are either bound by latency or UI driven is non-depterminism, and in larger apps this can add up quickly. 对于受延迟或UI驱动约束的许多应用程序来说,最糟糕的事情是非破坏性,而在较大的应用程序中,这可能会很快增加。

  2. !((object)a == (object)b) and (object)a != (object)b are not always compiled down to the same code , as is true certianly for !(a == b) and a != b, even without any explicit operators or Equals overrides. !((object)a ==(object)b)和(object)a!=(object)b并不总是编译成相同的代码 ,因为!(a == b)和a!= b,即使没有任何明确的运算符或Equals覆盖。 A '(object)a != (object)b' is far more likely to trigger .Net's own more pedantic call down into the runtime which is very expensive. 一个'(对象)a!=(对象)b'更有可能触发.Net自己更迂腐的调用运行时非常昂贵。

  3. Guarding early and often with '(object)' or 'RefEquals' if very helpful to the JIT even if you currently dont have operator or Equals overrides. 如果对JIT非常有帮助,即使您目前没有运算符或等于覆盖,也要尽早使用'(object)'或'RefEquals'进行保护 (object) is even better. (对象)更好。 If you think about what the JIT has to do to detemine if a type could have overrides, and deal with the bizantine (sp) Equality rules and whatnot, its like a mini hell, and anyting that could be made public later and you intend ref equality you save yourself from a sudden slowdown or wonky code later. 如果你考虑JIT必须做什么来确定一个类型是否可以覆盖,并处理bizantine(sp)Equality规则等等,它就像一个迷你地狱,以及任何可以在以后公开的任何东西,你打算参考平等,你可以避免突然放缓或以后的代码。 If its already is public and not sealed, the JIT cant quarentee that the rules will or have time to chase them down. 如果它已经是公开的而不是密封的,那么JIT就不能保证规则会或者有时间追查它们。

  4. Guarding the generally more pervasive 'null' checks is probaly even more important , though not a part of the OP's question, as the same rules and issues generally apply. 保护通常更普遍的“无效”检查可能更为重要 ,尽管不是OP问题的一部分,因为相同的规则和问题通常适用。 '(object)a == null' and '!((object)a == null)' being the 'pedantic' equivalents. '(object)a == null'和'!((object)a == null)'是'迂腐'的等价物。

The overhead of Object.ReferenceEquals is only in loading the arguments, which will be JITted away in most scenarios. Object.ReferenceEquals的开销仅在加载参数时,在大多数情况下都会被JIT实现。 After that, both Object.ReferenceEquals and operator== come down to a single IL ceq operator. 之后,Object.ReferenceEquals和operator ==都归结为单个IL ceq运算符。 At worst, the difference will be insignificant. 在最坏的情况下,差异将是微不足道的。

More importantly, Object.ReferenceEquals is much more intention-revealing than (object)o1 == (object)o2. 更重要的是,Object.ReferenceEquals比(object)o1 ==(object)o2更有意图揭示。 It states clearly in the code "I am testing for reference equality / identity", rather than hiding the intent under a bunch of casts. 它在代码“我正在测试引用相等/身份”中清楚地说明,而不是将意图隐藏在一堆强制转换下。

The previously mentioned article about == operator being better provides incomplete information, at least on .NET 4.0 (well it was written back in 2.0 times). 前面提到的关于==运算符更好的文章提供了不完整的信息,至少在.NET 4.0上(很好地它被写回2.0次)。

It states ReferenceEquals is not being optimized/inlined, which is true only if you build your project with 'AnyCPU' configuration. 它声明ReferenceEquals没有被优化/内联,只有在使用'AnyCPU'配置构建项目时才会出现这种情况。 Setting to either 'x86' or 'x64' makes (object)obj == null and ReferenceEquals(object, null) end up being identical IL, where both methods are simply one 'ceq' IL instruction. 设置为'x86'或'x64'使(对象)obj == null和ReferenceEquals(object,null)最终成为相同的IL,其中两个方法都只是一个'ceq'IL指令。

So the answer is: the IL produced by both methods is identical on a Release / x86 or x64 builds. 所以答案是:两种方法生成的IL在Release / x86或x64版本上是相同的。

ReferenceEquals is definitely more readable, at least to my taste. ReferenceEquals肯定更具可读性,至少符合我的口味。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 Object.Equals(obj1,obj2)vs obj1.Equals(obj2)? - Object.Equals(obj1, obj2) vs obj1.Equals(obj2)? obj1.Equals(obj2)和c#中的静态Object.Equals(obj1,obj2)有什么区别? - What's the difference between obj1.Equals(obj2) and static Object.Equals(obj1, obj2) in c#? 我怎么知道对象完全像obj1 = obj2一样被改变了? - How can I know that object is changed totally like obj1 = obj2? 转换清单 <OBj1> 列出 <Obj2> 在Windows Phone 8中 - Convert List<OBj1> to List<Obj2> in windows phone 8 你如何创建一个类似数组的C#构造函数,它将允许“new MyClass(){obj1,obj2,obj3};” - How do you create an array-like C# constructor that will allow “new MyClass() { obj1, obj2, obj3 };” 这个语法有什么作用? if(obj是SomeType obj2) - What does this syntax do? if(obj is SomeType obj2) 什么是Object.Equals(obj,null)和obj == null之间的区别 - Whats the Difference between Object.Equals(obj, null) and obj == null ReferenceEquals(null,obj)与null == obj是一回事吗? - Is ReferenceEquals(null, obj) the same thing as null == obj? object.ReferenceEquals或==运算符? - object.ReferenceEquals or == operator? 为什么Assert.AreEqual(T obj1,Tobj2)使用相同的字节数组失败 - Why does Assert.AreEqual(T obj1, Tobj2) fail with identical byte arrays
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM