On Sunday, 4 January 2015 at 03:14:31 UTC, Andrei Alexandrescu
wrote:
On 1/3/15 5:30 PM, Jonathan Marler wrote:
I've recently looked at how the '==' operator works with
classes. I was
disappointed to find that 'a == b' always gets rewritten to:
.object.opEquals(a, b);
The reason for my disappointment is that this results in
unnecessary
overhead. I would think that the compiler would first try to
rewrite the
'==' operator using a type-specific opEquals method, then fall
back on
the generic version if one did not exist. Is there a reason
for this?
TDPL has a detailed explanation of that, including a reference
to Java's approach. There's less overhead in calling the free
function in object (it's inlinable and if e.g. the references
are equal there's no virtual call overhead).
Andrei
Can you point me to that detailed explanation?
The problem I see is that in almost all cases the
opEquals(Object) method will have to perform a cast back to the
original type at runtime. The problem is this isn't doing any
useful work. The current '==' operator passes the class as an
Object to a generic opEquals method which eventually gets passed
to a method that must cast it back to the original type. Why not
just have the == operator rewrite the code to call a "typed"
opEquals method? Then no casting is necessary.
I wrote a quick performance test to demonstrate the issue.
import std.stdio;
import std.datetime;
class IntWrapper
{
int x;
this(int x)
{
this.x = x;
}
override bool opEquals(Object o)
{
IntWrapper other = cast(IntWrapper)o;
return other && this.x == other.x;
}
bool opEquals()(auto ref const IntWrapper other) const
{
return this.x == other.x;
}
}
void main(string[] args)
{
size_t runCount = 2;
size_t loopCount = 10000000;
StopWatch sw;
IntWrapper x = new IntWrapper(1);
IntWrapper y = new IntWrapper(1);
bool result;
for(auto runIndex = 0; runIndex < runCount; runIndex++) {
writefln("run %s (loopcount %s)", runIndex + 1, loopCount);
sw.reset();
sw.start();
for(auto i = 0; i < loopCount; i++) {
result = x.x == y.x;
}
sw.stop();
writefln(" x.x == y.x : %s microseconds",
sw.peek.usecs);
sw.reset();
sw.start();
for(auto i = 0; i < loopCount; i++) {
result = x.opEquals(y);
}
sw.stop();
writefln(" x.opEquals(y) : %s microseconds",
sw.peek.usecs);
sw.reset();
sw.start();
for(auto i = 0; i < loopCount; i++) {
result = x.opEquals(cast(Object)y);
}
sw.stop();
writefln(" x.opEquals(cast(Object)y): %s microseconds",
sw.peek.usecs);
sw.reset();
sw.start();
for(auto i = 0; i < loopCount; i++) {
result = x == y;
}
sw.stop();
writefln(" x == y : %s microseconds",
sw.peek.usecs);
}
}
Compiled with dmd on Windows(x64):
dmd test.d -O -boundscheck=off -inline -release
run 1 (loopcount 10000000)
x.x == y.x : 6629 microseconds
x.opEquals(y) : 6680 microseconds
x.opEquals(cast(Object)y): 89290 microseconds
x == y : 138572 microseconds
run 2 (loopcount 10000000)
x.x == y.x : 6124 microseconds
x.opEquals(y) : 6263 microseconds
x.opEquals(cast(Object)y): 90918 microseconds
x == y : 132807 microseconds