20

Is there extra overhead in using the object.ReferenceEquals method verses using ((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. 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 ! preceding object.ReferenceEquals, whereas the != in the first variation is hard to overlook.

Kevin Reid
  • 37,492
  • 13
  • 80
  • 108
Triynko
  • 18,766
  • 21
  • 107
  • 173

6 Answers6

23

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.

Konrad Rudolph
  • 530,221
  • 131
  • 937
  • 1,214
  • I agree the cast to object is confusing. One may wonder why the cast in ((object)obj == null) is necessary, not realizing that reference equality ultimately boils down to (object == object). Also, comparing to null seems implicitly a reference comparison, but the typed operator is actually chosen. – Triynko Apr 09 '09 at 20:44
  • It will not necessarily be inlined. Depends on hwo the JIT is feeling for a particular set of code. I have definately seen it not inline. This holds true for many of the small helper functions. It can be way slower depending on what you are doing. In a loop is more likely to get inlined, though not guarenteed. – Beeeaaar Dec 26 '12 at 20:45
  • @Celess It even more depends on the JIT used: the x64 JIT should inline it, the x86 JIT probably won’t (unfortunately) because it’s reluctant to inline even the most obvious cases. But your caveat is correct so I’ve updated the answer to qualify the statement. – Konrad Rudolph Dec 26 '12 at 22:32
  • 1
    The article you reference is outdated. In .NET 4.0 (I checked 4.6) the generated IL is byte-for-byte equal between the two methods. The method call to `ReferenceEquals` does not even appear anymore in the IL. – Abel Nov 19 '17 at 01:53
5

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.

nawfal
  • 70,104
  • 56
  • 326
  • 368
  • 2
    Like almost all published microbenchmarks, you didn’t consider the variance of the samples. Proper microbenchmarks *need* to include statistical significance analysis, otherwise the whole result could be a fluke. Just repeating the tests a few times is not enough. Furthermore, to get an authoritative answer here we should look at the generated code (not IL but machine code generated by the JIT), that would show us everything we need to know. The problem is that the x86 JIT doesn’t actually perform a lot of inlining though, so your results are plausible. (On an x64 the results should differ.) – Konrad Rudolph Dec 16 '12 at 13:19
  • 1
    That’s not what I mean by variance. I mean [variance](https://en.wikipedia.org/wiki/Variance) in the narrow sense in statistics and in particular in the context of [statistical significance testing](https://en.wikipedia.org/wiki/Significance_testing). Note, I still think this is a good answer, hence +1. – Konrad Rudolph Dec 16 '12 at 19:39
  • @nawfal Can we assume that the negation of all of these is the same (`(object)p1 != (object)p2`)? I would think no so it might be cool to run the tests on those as well... :) – christo8989 Sep 15 '15 at 17:55
3

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.

Beeeaaar
  • 1,010
  • 9
  • 19
  • 1
    +1 Your points are intriguing. Have you written about this in detail in another question or blog post? I would like to hear more about how you came to these conclusions. – David Schwartz May 20 '14 at 21:49
3

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.

itowlson
  • 73,686
  • 17
  • 161
  • 157
  • I agree that the casts could be misleading, but don't you think that the '==' operator is pretty universally recognized as a reference comparison? – Craig Otis Apr 09 '09 at 19:27
  • 6
    No, on the contrary. For example, string1 == string2 performs a value comparison rather than a reference comparison. Hence the original poster's need to cast to object to force a reference comparison. – itowlson Apr 09 '09 at 19:31
  • Re your first sentence: I believe it's exactly the other way round (at least that's the way it's implemented in Rotor) but it doesn't really matter. – Konrad Rudolph Apr 09 '09 at 19:32
  • I also believe the first sentence is the other way around. The reflected c# source of ReferenceEquals is implemented as object == object. That's what led me to use (object) casting for reference equality, as I thought it might bypass the function call and be faster. – Triynko Apr 09 '09 at 20:34
  • Thanks Konrad and Triynko, you are right. I have edited the post to correct the error. – itowlson Apr 09 '09 at 21:17
2

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.

Agata
  • 51
  • 1
  • 2
  • Though it comes down to negligible difference, there is some difference anyway.. See my answer which is on x86 platform.. – nawfal Dec 16 '12 at 08:42
0
public static bool ReferenceEquals (Object objA, Object objB) {
        return objA == objB;
    }

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

mynkow
  • 4,408
  • 4
  • 38
  • 65