RE: Bug in Double.java

From:

Eric Blake

Subject:

RE: Bug in Double.java

Date:

Tue, 24 Jul 2001 18:26:04 +0100

> -----Original Message-----
> From: Tom Tromey [mailto:address@hidden
> Sent: 23 July 2001 21:35
> To: Eric Blake
> Cc: classpath; address@hidden
> Subject: Re: Bug in Double.java
>
> Eric> I also spotted some optimizations to add. I made the equals,
> Eric> compare, and isNaN work without needing to use
> Eric> doubleToLongBits(), since a native call carries overhead.
>
> I think we definitely need to implement some kind of preprocessing
> step for Classpath and libgcj. The above reasoning is exactly
> backwards for gcj.
For isNaN(), the implementation
return x != x;
seems like it would be faster than
return doubleToLongBits(value) == 0x7ff8000000000000L;
even in gcj, since the compiler must already preserve the semantics of !=,
and this avoids a method call.
But you do have a point on the other methods that I optimized away from
native calls: when _everything_ is compiled to native code, the
implementation of doubleToLongBits() is more or less a reinterpret_cast<>
which can be inlined and optimized, but my implementation involves the
overhead of floating-point division for comparing 0.0 and -0.0 which is less
likely to be optimized. Having two versions is definitely worth looking
into.
> Eric> I also updated hashCode to use a cache, to reduce the number of
> Eric> native calls made by the same Double.
>
> This one too. For libgcj this one probably makes things even less
> efficient, since it increases the size of a Double.
>
After more thought, I'm not sure if caching the hashCode buys much. As you
point out, it makes every Double larger; and Double is probably too deeply
intertwined with most VMs to start changing (just look at how many native
calls there are to implement reflection!). Besides, it seems to me that
most code creates new Double objects rather than reusing existing ones, and
creating hashtables with Double keys is also rare. The cache is only useful
if hashCode() is likely to be called multiple times on the same Double,
which I am doubting.
> In this case I guess we can just do as Mark suggests and put stuff
> into Configuration. Then the compiler will remove the unused branch.
>
> I think I'd prefer not to have one big `GCJ' configuration name
> though. For this one, maybe `FAST_NATIVE_CALLS' or `CNI' is what we
> should use. Comments?
FAST_NATIVE_CALLS sounds like the better name to me. Using compile-time
constants, the choice between native methods and pure Java has no overhead
within a method, because the dead branch should not appear in the .class
file. So, for example, the implementation of equals might look like this:
public boolean equals(Object obj)
{
if (!(obj instanceof Double)) return false;
double d = ((Double) obj).value;
if (Configuration.FAST_NATIVE_CALLS)
return doubleToLongBits(value) == doubleToLongBits(d);
else
{
// common case first, then check NaN and 0
if (value == d)
return (value != 0) || (1/value == 1/d);
return isNaN(value) && isNaN(d);
}
}
On the other hand, deciding whether to add a field or not DOES require
either a preprocessor or two versions of the .java file in different
directories. This is even more reason to scrap the idea of caching the
hashcode of a Double.
>
> Tom
One other thing to consider - do we really need to require both a native
doubleToLongBits and doubleToRawLongBits? To me, it might make more sense
to implement the former in Java (here, isNaN would have to be implemented
with d!=d to avoid infinite recursion):
public long doubleToLongBits(double d)
{
if (isNaN(d)) return 0x7ff8000000000000L;
return doubleToRawLongBits(d);
}
--
Eric Blake, Elixent, Castlemead, Lwr Castle St., Bristol BS1 3AG, UK
address@hidden tel:+44(0)117 917 5611