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


Reply via email to