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

后端 未结 6 1855
攒了一身酷
攒了一身酷 2020-12-10 11:31

Is there extra overhead in using the object.ReferenceEquals method verses using ((object)obj1 == (object)obj2)?

In the first case, there wo

相关标签:
6条回答
  • 2020-12-10 11:47

    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.

    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. Same is true with NullOrEmpty vs '(object)str == null || 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.

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

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

    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. (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. If its already is public and not sealed, the JIT cant quarentee that the rules will or have time to chase them down.

    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. '(object)a == null' and '!((object)a == null)' being the 'pedantic' equivalents.

    0 讨论(0)
  • 2020-12-10 12:04

    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).

    It states ReferenceEquals is not being optimized/inlined, which is true only if you build your project with 'AnyCPU' configuration. 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.

    So the answer is: the IL produced by both methods is identical on a Release / x86 or x64 builds.

    ReferenceEquals is definitely more readable, at least to my taste.

    0 讨论(0)
  • 2020-12-10 12:05

    Contrary to answers here, I found (object) == faster than 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.

    Platform: 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.

    Results were consistent in Debug configuration too...

    As pointed out, emphasis should be on Readability/meaningfulness/revealing intent.

    0 讨论(0)
  • 2020-12-10 12:06

    Is there extra overhead in using the object.ReferenceEquals method

    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.

    About readability: I personally think that object.ReferenceEquals is potentially more readable (even in the negated form) because it explicitly expresses its semantics. The cast to object may be confusing to some programmers.

    I've just found an article discussing this. It prefers (object)x == y because the IL footprint is smaller. It argues that this might facilitate inlining of method X using this comparison. 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.

    That is to say: choosing one way over the other will have no impact whatsoever on the JIT and consequently on performance.

    0 讨论(0)
  • 2020-12-10 12:10
    public static bool ReferenceEquals (Object objA, Object objB) {
            return objA == objB;
        }
    

    http://referencesource.microsoft.com/#mscorlib/system/object.cs,4d607d6d56a93c7e

    0 讨论(0)
  • 2020-12-10 12:13

    The overhead of Object.ReferenceEquals is only in loading the arguments, which will be JITted away in most scenarios. After that, both Object.ReferenceEquals and operator== come down to a single IL ceq operator. At worst, the difference will be insignificant.

    More importantly, Object.ReferenceEquals is much more intention-revealing than (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.

    0 讨论(0)
提交回复
热议问题