Implementing Lambda with Capture support makes Metaspace fills LambdaForms$BMH class
Vladimir Ivanov
vladimir.x.ivanov at oracle.com
Fri May 5 12:44:32 UTC 2017
Jochen,
>> Can you elaborate a bit, please? What kind of stress do you observe: MH
>> instantiation overhead or increased footprint? Does memory increase come
>> from method handles alone or there are plenty of classes loaded at
>> runtime for compiled LFs?
>
> My biggest problem in terms of creation performance are transformations
> of the handle using asType so far. Having to create many many different
> MethodHandles increases the memory footprint, but probably stabilizes.
> As for plenty of classes... well.. potentially yes. I can easily create
> such a program in Groovy.
>
>>> example... foo(x,y) is mapped to MyInvokerFallback.handle(receiver,
>>> "foo", x, y); with the method taking a String and an Object[]. How do I
>>> get the name in there without insertArguments? Don't I have to create at
>>> least one handle per name I find?
>>
>> One important detail is how method handles are actually used.
>>
>> Yes, you do have to create a method handle per call site, but it is
>> placed in a CallSite instance and bound to indy call site. In that case,
>> there's no need in LambdaForm specialization: JIT-compiler will inline
>> the whole method handle chain at indy call site which is equivalent to
>> bytecode specialization.
>
> is that now true for all handles? Since the forms do no longer show up
> in the traces I cannot tell. Also I am required to have MutableCallsite,
> since I have to handle the dispatch based on runtime types. This
> multiplies the number of handles I create. Example:
Yes, it's true for all handles. LF specialization is tightly coupled
with JIT-compilers and is triggered only for method handles which aren't
inlined into all callers. It never happens for indy call sites - JITs
can always inline (and do so) through them. (Even when they are linked
to mutable CSs. In that case, there's a dependency on compiled method
registered to track future modifications.)
But I suspect it's not what you asked about.
FYI with -XX:+ShowHiddenFrames the JVM will include LF frames in stack
trackes. But it's not about stack frames: there's still a single frame
per method handle in a method handle chain in interpreter.
LambdaForm specialization is about generating a dedicated class for a
LambdaForm instance.
So, irrespective of LF specialization, you'll observe the same number of
stack frames, but the methods being executed will refer to either shared
or customized LFs.
In other words, LF specialization influence how many classes for
compiled LFs are loaded, but doesn't change what actually happen during
MH invocation. (No inlining on bytecode level is needed during
specialization. JIT will already do that during compilation. No need to
help it.)
> Object myMethod(Object singleArg);
> Object myMethod(String singleArg);
>
> myMethod(x)
>
> In Java, now depending on the defined type of x we know which of the two
> methods to call. Which means, if I could use a static callsite here. In
> Groovy I have to first put in a handle, that directs to my method
> selector, which will then install the target handle (and call it), as
> well as a guard to check that the argument is as expected.
I'd like to differentiate method handles and lambda forms. If you create
a new method handle, it doesn't imply a new lambda form is also created.
Method handles aren't compiled to bytecode themselves, only lambda forms
are. So, when you instantiate a new method handle, from footprint
perspective you pay a cost of a single object instance. Most likely, the
costs of the lambda form & associated class are amortized across all
method handles which share them.
For example, my experiments with Nashorn showed 1000x ratio between
instantiated MHs & LFs (millions handles vs thousands LFs on Octane
benchmarks).
Also, LF caches are SoftReference-based, so footprint measurements don't
reflect how many LFs are actually used. It's pretty expensive to
construct a LF, so it's benefitical to keep it alive longer that weak
references allow.
You mentioned MH.asType() and, unfortunately, from LF sharing
perspective it's a weak point right now. There's some sharing possible,
but the current LF shape for asType() transformation is hard to share.
It hasn't been addressed yet mostly because we don't have a good
understanding how much overhead does it cause. So, if you have any data
on that, please, share.
>> Also, LambdaForms are aggressively shared, so you shouldn't observe
>> significant growth in their number at runtime (unless there are lots of
>> unique "erased" signatures present; that's where LF sharing can't help
>> now).
>
> there is a high number of "runtime signatures"
What is important is how many unique erased signatures exist (erased to
basic types [1]). It's still possible to trigger explosion in number of
LFs (5^255 is still pretty large, isn't it? ;-)), but now it's a corner
case.
Best regards,
Vladimir Ivanov
[1] 5 in total: int, long, float, double, Object
More information about the mlvm-dev
mailing list