On 04/22/2014 09:53 AM, Peter Levart wrote:
On 04/22/2014 02:48 PM, David M. Lloyd wrote:
On 04/22/2014 04:17 AM, Peter Levart wrote:
On 04/22/2014 12:02 AM, David M. Lloyd wrote:
Um, do we *know* that there is a performance cost to covariantly
overriding these methods? There would seem to be enough information
to keep them monomorphic, if the optimizer is smart enough to inline
the bridge methods and the delegating override method. The overridden
methods in addition can be final, meaning that in the 99% case that
you're invoking directly on the desired buffer type, it should be just
as optimizable for the same reason that the original methods were
optimizable. The only potentially "slow" invocation path is if you
call the method on a Buffer reference, and even then it seems like
there's enough information to avoid slowness - and if not, then that
seems like a HotSpot problem that is very solvable ("if all overrides
of this method call super.xxx(), inline & eliminate them").
It's more complicated than that. Maybe we need an expert for hotspot JIT
to answer this question, but as the code is written in the Rickard's
webrev, then the reasoning behind the JIT to keep the monomorphic
dispatch would have to be more involving. Richard is doing the following
(in ByteBuffer):
@Override
public ByteBuffer position(int newPosition) {
super.position(newPosition);
return this;
}
javac compiles each of the covariant overrides as two methods - one that
actually "overrides" the virtual method in superclass (has the same
signature) and calls the covariant-returning method with a virtual
dispatch. So ByteBuffer.position(int) is compiled as:
public ByteBuffer position(int newPosition) {
super.position(newPosition);
return this;
}
public Buffer position(int newPosition) {
// this is an invokevirtual for position:(I)Ljava/nio/ByteBuffer;
return (ByteBuffer) position( (int) newPosition);
}
If the methods were final, AFAICT it'd be more like this:
public final ByteBuffer position(int newPosition) {
// iirc super upcall is already bytecoded as invokespecial
invokespecial (Buffer)Buffer.position(newPosition);
return this;
}
public synthetic final Buffer position(int newPosition) {
return effectively-invokespecial
(ByteBuffer)ByteBuffer.position(newPosition);
}
Since there would only be one possible target for the invokevirtual,
my understanding is that the JIT will convert that into an
invokespecial, letting the whole works be optimized at worst and
inlined at best.
There are multiple possible targets for invokevirtual
position:(I)Ljava/nio/Buffer; - all the methods that override it in all
subclasses loaded. It doesn't matter if they are final or not (only if
they are effectively final or not). The non-finality of a method has a
performance impact only if the method *is* overridden in any of the
loaded subclasses, otherwise it is effectively final and treated as such
by JIT (at least that's how I understand it - any hotspot JIT expert
please debunk my claims).
That is only true if you are calling the method on the base class, which
is normally seldom done (though it will be nearly always in legacy code,
but see below).
That might also be the answer to why the synthetic method need not be
marked as final if the covariant method is. The synthetic method can
never be overridden in a sub-class (at least not by javac) - only the
covariant method can be.
Doesn't sound quite right to me, but I'll defer to any experts who might
wish to discuss it on compiler-dev.
But as Paul noted, the methods on Buffer are probably not used in hot
loops alone, since they are just for reading/adjusting
position/limit/mark. The hot loops probably also contain methods for
reading/writing the buffer and those are only defined on particular
sub-types of java.nio.Buffer, so it can reasonably be expected that the
static (guaranteed) type of target upon which methods are called in hot
loops is a particular subtype of java.nio.Buffer and JIT only has one
method to choose from in this case.
Yeah new code will call the covariant final method directly in almost
all cases for sure, having been compiled against it. The tricky case is
old code - even if the JIT can figure out that it is really a subclass
being invoked upon, it'd still be forced to target the non-covariant
synthetic method due to the specific method reference in the bytecode.
Hopefully at this point though, as you say, the lack of overriding
classes will be enough to optimize the dispatch.
--
- DML