Sorry for the duplicate e-mail, this e-mail should be part of the mailing
thread.
Hi Ethan,
I could also see the performance regression. I was able to basically get rid of
the regression, the actual code change is quite minimal.
The performance regression's culprit is Parser::map2, which is used widely in
this codebase. It generates PStep.Good/Pstep.Bad instances which store strict
final States. When flattening is enabled, we permit non-atomic flattening
layouts for these, which allows C2 to scalarize the layout of the State within
PStep.Good and PStep.Bad.
The problem comes up because of the extreme parametricity of this code along
with the megamorphic nature of the lambda. Let's look at it.
We have:
public interface Parser<Context, Problem, Value> extends Function1<
State<Context>,
PStep<Context, Problem, Value>
>
public interface Function1<T1, R> extends Serializable, Function<T1, R> {
R apply(T1 var1);
}
With erasure applied, we get that Parser has a method Object apply(Object
var1). This means that the returned lambda from map2 effectively has the type
Object -> Object.
We would like for C2 to see that this is actually State -> PStep.
Unfortunately, C2 can't see that, because the parsers are too megamorphic, so
we end up with virtual calls. This forces us to abide by the ABI which Object
-> Object requires, which is all about reference objects.
Thus, we end up with this compiled pseudo-code:
map2Lambda(oop<State> s0) {
oop<PStep.Good> r0 = parserA(s0);
oop<State> s1 = allocate_instance_from_flattened<State>(r0);
oop<Pstate.Good> r1 = parserB(s1);
return r1;
}
So, each time we're calling this lambda, we have to allocate a new instance
from the extracted State in PStep.Good.
We can help C2 out, by making our apply method carry some more type info, by
giving Parser this override:
@Override
PStep<Context, Problem, Value> apply(State<Context> state);
Now C2 sees that we will only ever take in State and produce PSteps, so it can
pick a different calling convention, avoiding the allocation before applying
parserB.
The majority of the regression disappears with this. map2 still seems like it's
slightly slower according to my samples.
Thanks for the report!
Johan Sjölén