Hi all,
reminded by that mail from MG I decided it is best to forget about
writing something formal or anywhere near detailed instructions and just
write what I think should be done and what the current state is. And
don't get me wrong... this is neither the first try nor the first
version of the logic I am trying to show.
How things currently are working is that we do a double dispatch. First
a java style call to the bootstrap method, that then calls another
bootstrap method. Reason is the first call will have only static types,
while the second call will have the real types and our dispatch depends
on the real types. We then go into our cache, which is broken for static
method calls btw, and if there is no fitting method we select another
one with our method selection to then create a MethodHandle for this,
which we then invoke ourselves. That is a super short version of what we
do in hundreds of lines of code that is thanks to the MethodHandles API
not very nice to read.
There are, besides bugs, 2 problems with this approach:
(1) we invoke the method in IndyInterface, which is not great when it
comes to things like @CallerSensitive or the module system.
(2) we create a lot of MethodHandles on the first run. The later is
especially a problem in bigger applications if they tend to visit a lot
of code only a few times. Creating MethodTypes for MethodHandles is
quite expensive, more so than a reflective call. Or our old call site
caching. Yes, in the end MethodHandles are faster, but only once
optimizations have run. And that takes thousands of iterations. MG for
example has been hit by this.
Now what I think how things should be...
first of all, we need a proper slow path. That is where the invocation
will not be the fastest, but otherwise cheap. Our method selection
itself is not that slow.
So I think what should happen is method selection and then reflective
information on the MetaMethod to get the Method and then not invoke, but
set it as target or fall back to the invocation of the MetaMethod plus
exception unwrapping. I call this SLOW_PATH_0 In pseudo code:
SLOW_PATH_0 = {
metaMethod = selectMetaMethod(receiverAndarguments)
if (metaMethod has Method) {
continue with Method.invoke
} else {
continue with MetaMethod.invoke
}
}
We can also add a counter to the callsite and say that if this slow path
is called X times, we want to have something more specific.
callsite target = {
if (counter < THRESHOLD) {
increase counter for slow path
continue with SLOW_PATH_0
} else {
reset counter for slow path
install FAST_PATH[1] /* new callsite target */
continue with SLOW_PATH_0
}
}
That means the invocation would at first install the handle, but the new
handle would be actually used only on the next call.
FAST_PATH[1] would basically consist of
if (receiverAndArguments fit FAST_PATH[1] unreflected Method) {
continue with unreflected Method
} else {
increase counter for slow path
continue with SLOW_PATH_0
}
And "install FAST_PATH[X]" would basically get the MetaMethod und if it
has a reflective Method we would unreflect that to create a handle and
install the handle as FAST_PATH[X]
From here on we could do different things. In this version we would end
up with the slow path all the time if our fast path selection was not
good. We can add more fast paths:
if (receiverAndArguments fit FAST_PATH[1] unreflected Method) {
continue with unreflected Method
} else {
if (receiverAndArguments fit FAST_PATH[2] unreflected Method) {
continue with unreflected Method
} else {
if (receiverAndArguments fit FAST_PATH[3] unreflected Method) {
continue with unreflected Method
} else {
reset counter for slow path
install FAST_PATH[4] /* new callsite target */
continue with SLOW_PATH_0
}
}
}
Above an example for 3 fast paths. As a note: I am using if-else this
way here because that represents a guard check in form of a MethodHandle
consisting of a handle to check the condition, one for the if-branch and
another for the else-branch. Of course we should not let that overgrow
or we will never get a chance to have this optimized. Let's say we limit
this to 3 and we have them in an array on the callsite object, we can
have something like this:
if (exists [1] and is fitting) {
continue with unreflected Method 1
} else {
if (exists [2] and is fitting) {
continue with unreflected Method 2
} else {
if (exists [3] and is fitting) {
continue with unreflected Method 3
} else {
increase counter for slow path
if (counter > threshold) {
install next FAST_PATH in callsite array
continue with SLOW_PATH_0
} else {
continue with SLOW_PATH_0
}
}
}
}
This could be even installed as ConstantCallsite, which is better for
optimization. As I said, on the callsite we could have an array with
elements of
class FastPath {
Class[] receiverAndArguments
MethodHandle unreflectedMethod
}
plus a
fastPaths = new FastPaths[3]
lastFastPath = 0
and "install next FAST_PATH" would be then:
if (lastFastPath<3) {
callsite.fastPaths[callsite.lastFastPath++] = new FastPath(..)
}
or with a simple method of rotation:
callsite.fastPaths[2] = callsite.fastPaths[1]
callsite.fastPaths[1] = callsite.fastPaths[0]
callsite.fastPaths[0] = new FastPath(..)
or we add aging:
increase age
if (exists [1] and is fitting) {
set age[1]
continue with unreflected Method 1
} else {
if (exists [2] and is fitting) {
set age[2]
continue with unreflected Method 2
} else {
if (exists [3] and is fitting) {
set age[3]
continue with unreflected Method 3
} else {
increase counter for slow path
if (counter > threshold) {
install next FAST_PATH in callsite array
continue with SLOW_PATH_0
} else {
continue with SLOW_PATH_0
}
}
}
}
and
class FastPath {
Class[] receiverAndArguments
MethodHandle unreflectedMethod
long age
}
and "install next FAST_PATH" would be then:
sort callsite.fastPaths by age
rotate out oldest from callsite.fastPaths
callsite.fastPaths[0] = new FastPath(..)
Of course there is a lot more to consider... like avoiding that the
Class-Objects in FastPath are hard referenced forever. There is also the
switch for category methods to consider and a lot of details. But I
first want to see if anybody can even follow this ;)
bye Jochen