jlink plugin experiment to improve jake startup time
mandy.chung at oracle.com
Tue Oct 6 15:19:21 UTC 2015
> On Oct 6, 2015, at 4:38 AM, Claes Redestad <claes.redestad at oracle.com> wrote:
> Hi Mandy,
> On 2015-10-05 23:13, Mandy Chung wrote:
>> I have been experimenting a jlink plugin to improve the module system startup time. On a Quad-Core Intel Xeon E5 @3.7 GHz, 16G memory (Mac Pro) machine, the module system startup takes 110 ms and 75% of it is to reconstitute 64 module descriptors with 2388 packages altogether.
>> This high overhead includes initializing lambda forms (class loading, linking, initialization), parsing of module-info.class and compilation of many methods during startup.
>> The attached charts shows the summary of module system startup
>> 1) jake-b83 (as of Sep 29)
>> module bootstrap time 110 ms
>> module descriptor reconstitution 82 ms
>> VM startup time is 3x of jdk9-b83 vm startup time (148ms vs 47 ms)
>> 2) No lambda at startup
>> module bootstrap time 66 ms (saved 44 ms compared to #1)
>> module descriptor reconstitution 33.6 ms (saved 48.4ms)
>> VM startup time is 2.2x of jdk9-b83 (105ms vs 47ms)
>> 3) jlink plugin to generate a .class file to build module descriptors at link time
>> module bootstrap time 50.7 ms (saved 15.3ms compared to #2)
>> module descriptor reconstitution 16.8 ms (saved 16.8ms)
>> VM startup time is 1.9x of jdk9-b83 (90ms vs 47ms)
> Is it safe to assume that #3 include the changes in #2?
#2 and #3 are different changes. ModuleDescriptor.Requires::modsValue is one use of lambda that is changed in both #2 and #3. Other change of Stream::forEach in ModuleDescriptor in #2 are not in #3.
#3 has its own builder to create ModuleDescriptor. Stream::forEach is used in 3 places in ModuleDescriptor in the loop validating the strings passing in the Exports and Provides constructor for example that is skipped in this installed module descriptor reconstitution.
#2 is mainly to measure the overhead due to lambda at startup to help understand what attributes to 82ms.
>> For #3, it parses module-info.class at link time and validates all names. It generates a .class file to call a custom Builder to create ModuleDescriptor for the installed modules. At runtime, the generated class will construct the Builder and ModuleDescriptor objects will skip name validation, no defensive copy of the input set/map and skip reading and parsing of module-info.class (this is done by a special module descriptor builder class that doesn’t use lambda. This special builder is only used by installed modules).
> Since you're generating and loading classes that would otherwise not be loaded, have you done any footprint measurements?
The charts show the number of loaded classes and heap usage for these 3 runs:
692 classes are loaded in #1 and 503 classes are loaded in #3. Heap usage after GC in #1 and #3 is 1443 KB 1380 KB respectively.
> Alternatively, wouldn't it be possible for the plugin to modify the module-info.class directly?
Yes it’s possible but I don’t see how this can improve the startup time.
>> It saves 15.3 ms (23% of the module system bootstrap time in #2). The downside of this optimization is a little harder to make change and diagnose (this plugin implementation is straight forward though). There may be other small optimization to explore that could be done at jlink time (e.g. BuiltinClassLoader maintains a package to module map that can be constructed with a specific size to avoid map resizing).
>> What’s your thought/opinion to integrate this jlink plugin into jake?
> In general I think improving startup by staving off the first use of lambda isn't very helpful except for trivial applications (which we often over-emphasize when testing startup), while moving module validation to link time seems more like a real gain. It's a bit hard to tell if it's worth it without having seen a patch for the prototype, though.
I hope that LambdaForm initialization can be speed up so that it won’t incur significant overhead at startup time. As I explain above, ModuleDescriptor.Requires::modsValue is the only method changed from Stream::forEach to foreach loop.
More information about the jigsaw-dev