I just came across something pretty weird to me : when you use the Equals() method on a value type (and if this method has not been overriden, of course) you get something very very slow -- fields are compared one to one using reflection ! As in :
public struct MyStruct{
int i;
}
(...)
MyStruct s, t;
s.i = 0;
t.i = 1;
if ( s.Equals( t )) /* s.i will be compared to t.i via reflection here. */
(...)
My question : why does the C# compiler do not generate a simple method to compare value types ? Something like (in MyStruct's definition) :
public override bool Equals( Object o ){
if ( this.i == o.i )
return true;
else
return false;
}
The compiler knows what are the fields of MyStruct at compile time, why does it wait until runtime to enumerate MyStruct fields ?
Very strange to me.
Thanks :)
ADDED : Sorry, I just realize that, of course, Equals
is not a language keyword but a runtime method... The compiler is completely unaware of this method. So it make sens here to use reflection.
The following is the decompiled ValueType.Equals method from mscorlib:
public override bool Equals(object obj)
{
if (obj == null)
{
return false;
}
RuntimeType type = (RuntimeType) base.GetType();
RuntimeType type2 = (RuntimeType) obj.GetType();
if (type2 != type)
{
return false;
}
object a = this;
if (CanCompareBits(this))
{
return FastEqualsCheck(a, obj);
}
FieldInfo[] fields = type.GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
for (int i = 0; i < fields.Length; i++)
{
object obj3 = ((RtFieldInfo) fields[i]).InternalGetValue(a, false);
object obj4 = ((RtFieldInfo) fields[i]).InternalGetValue(obj, false);
if (obj3 == null)
{
if (obj4 != null)
{
return false;
}
}
else if (!obj3.Equals(obj4))
{
return false;
}
}
return true;
}
When possible, a bit-wise comparison will be done (note the CanCompareBits and FastEqualsCheck, both of which are defined as InternalCall. The JIT would presumably inject the appropriate code here. As to why it is so slow, I couldn't tell you.
It doesn't use reflection when it doesn't need to. It just compare values bit by bit in case the struct
if it can do so. However, there if any of the struct
members (or members of members, any descendants) override object.Equals
and provide its own implementation, obviously, it can't rely on bit-by-bit comparison to calculate the return value.
The reason it's slow is that the parameter to Equals
is of type object
and value types have to be boxed to be treated as an object
. Boxing involves allocating memory on the heap and memory copying the value type to that location.
You could manually provide an overload for the Equals
method that takes your own struct
as parameter to prevent boxing:
public bool Equals(MyStruct obj) {
return obj.i == i;
}
The idea of a compiler generated function is justified.
Thinking about the effects however I think the language design team did it right. Compilergenerated methods known from C++ are hard to understand for beginners. Lets see what would happen in C# with autogenerated struct.Equals:
As it is now, the concept of .Equals() is simple:
If the compiler would always create the Equals method, we could have:
Now our struct has an autogenerated override method that the code reader does not see! So how do you know that the base method Object.Equals does not apply to your struct? By learning all the cases of automatically compiler generated methods. And this is exactly one of the burdens learning C++.
Would consider it good decision to leave efficient struct Equals to the user and keep the concepts simple, requiring a standard default Equals method.
That said, performance critical structs should override Equals. The code below shows
3606 vs 53 Milliseconds measured on .Net 4.5.1
This performance gain is certainly due to avoiding virtual Equals, but anyway, so if the virtual Object.Equals would be called the gain would be much lower. Performance critical cases will not call Object.Equals however, so the gain here would apply.
using System;
using System.Diagnostics;
struct A
{
public int X;
public int Y;
}
struct B : IEquatable<B>
{
public bool Equals(B other)
{
return this.X == other.X && this.Y == other.Y;
}
public override bool Equals(object obj)
{
return obj is B && Equals((B)obj);
}
public int X;
public int Y;
}
class Program
{
static void Main(string[] args)
{
var N = 100000000;
A a = new A();
a.X = 73;
a.Y = 42;
A aa = new A();
a.X = 173;
a.Y = 142;
var sw = Stopwatch.StartNew();
for (int i = 0; i < N; i++)
{
if (a.Equals(aa))
{
Console.WriteLine("never ever");
}
}
sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds);
B b = new B();
b.X = 73;
b.Y = 42;
B bb = new B();
b.X = 173;
b.Y = 142;
sw = Stopwatch.StartNew();
for (int i = 0; i < N; i++)
{
if (b.Equals(bb))
{
Console.WriteLine("never ever");
}
}
sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds);
}
}
see also http://blog.martindoms.com/2011/01/03/c-tip-override-equals-on-value-types-for-better-performance/
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With