Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement AppCDS support for startup time improvement #5069

Closed
headius opened this issue Feb 28, 2018 · 9 comments
Closed

Implement AppCDS support for startup time improvement #5069

headius opened this issue Feb 28, 2018 · 9 comments
Milestone

Comments

@headius
Copy link
Member

headius commented Feb 28, 2018

I have been playing with the new Application Class Data Sharing ("AppCDS") feature of Oracle's JDK, and the results have been good.

Combining AppCDS with our own flags (tier 1, no JRuby JIT) I get the following improvements:

  • -e 1 goes from 1.5s to 1.1s
  • -S gem list goes from 2.5s to 1.9s

This may see an even bigger improvement on platforms with notoriously bad startup time, like Linux.

Given these numbers, I think we should move forward with implementing AppCDS support in our launchers.

My current vision is as follows:

  • The launchers will check on startup for an existing CDS cache. If present, it will be used.
  • If not present, the launchers will generate a new cache. This would be localized into the JRuby install dir, probably somewhere in lib. It would likely need to be localized per OpenJDK/OracleJDK version, since the cache format makes no binary compat guarantees.
  • All subsequent runs that can detect the CDS cache and proper JDK version will boot up using the cache.

This would not be difficult to add to the bash script, and would only take a bit more work to add it to the native launcher.

I used the following instructions to generate an AppCDS cache specific to JRuby: https://github.com/kkinnear/zprint/blob/master/doc/filter.md#the-manual-way

Here's my session, showing all flags for dumping and using a CDS cache:

[] ~/projects/jruby $ export GENERATE_CLASSLIST='-XX:+UnlockCommercialFeatures -XX:+UseAppCDS -Xshare:off -XX:DumpLoadedClassList=jruby.classlist'

[] ~/projects/jruby $ export GENERATE_CDS='-XX:+UnlockCommercialFeatures -XX:+UseAppCDS -Xshare:dump -XX:SharedClassListFile=jruby.classlist -XX:SharedArchiveFile=jruby.cache'

[] ~/projects/jruby $ export USE_CDS='-XX:+UnlockCommercialFeatures -XX:+UseAppCDS -Xshare:on -XX:SharedArchiveFile=jruby.cache'

[] ~/projects/jruby $ time jruby --dev -e 1

real	0m1.440s
user	0m1.951s
sys	0m0.180s

[] ~/projects/jruby $ export VERIFY_JRUBY=1 # move JRuby to normal classpath for CDS

[] ~/projects/jruby $ JAVA_OPTS=$GENERATE_CLASSLIST jruby --dev -e 1

[] ~/projects/jruby $ JAVA_OPTS=$GENERATE_CDS jruby --dev -e 1
Allocated shared space: 314572800 bytes at 0x0000000800000000
Loading classes to share ...
...

[] ~/projects/jruby $ JAVA_OPTS=$USE_CDS time jruby --dev -e 1
        1.18 real         1.67 user         0.18 sys
@headius headius added this to the JRuby 9.2.0.0 milestone Feb 28, 2018
@headius
Copy link
Member Author

headius commented Feb 28, 2018

cc @cl4es @enebo @kares @mkristian

@headius
Copy link
Member Author

headius commented Feb 28, 2018

We also should revisit "real" AOT for Ruby code, since there are now many technologies that could make precompiled bytecode load significantly faster than loading from source:

  • AOT can pre-compile some code.
  • jlink can cache things like constant pool method handles, which the JIT uses for binding all Ruby bodies
  • AppCDS can preprocess those precompiled Ruby classes and load and share them efficiently across runs.

It's possible we could get to the point where the only Ruby code being parsed and compiled at boot time is the user's application, allowing the features above to boot up JRuby, stdlib, and gems quickly.

@kares
Copy link
Member

kares commented Mar 1, 2018

We also should revisit "real" AOT for Ruby code

not sure I understand completely - mostly means IR persitence and re-use?
or do you mean smt like compiling the whole code-base into one large executable?

@enebo
Copy link
Member

enebo commented Mar 1, 2018

@kares I think he means saving java bytecode generated types for methods. Way back in the day this was attempted but it had two problems: verification overhead and permgen. I think even with verification disabled loading those classes still took a long time. we should not longer have the permgen issue either. If this is just one epic mmap this may be golden?

I think we are ok right now in that we only have conservative optimizations in IR so it should be safe to try. This will be a more complicated idea in the future as we need IR for profiled optimizations but then we will just dump that into another location so we can still load it. Pretty orthogonal.

@simonis
Copy link

simonis commented Mar 2, 2018

Does JRuby use the Application class loader to load most of it’s classes or does it use custom class loaders? AppCDS currently only works for the Application class loader out of the box. For Tomcat for example it only can cache about 30% of the classe (see https://simonis.github.io/CDS/cds.xhtml how you can fix it - but it’s still work in progress :)

@headius
Copy link
Member Author

headius commented Mar 2, 2018

@simonis Most of the classes that will load in the first few seconds of execution will just come from the normal system classloaders. Methods that JIT at runtime are usually loaded into their own classloaders, one per method, to allow them to GC.

If we produced .class files AOT, however, we could load those with the system classloader and AppCDS would still work. There's not as much need to make code loaded from shared .class files be individually GCable since it's a bounded set.

@mkristian
Copy link
Member

@headius just to be able to follow the discussion: with AOT you mean compiling ruby-file -> java-file -> class-file ?

@enebo enebo modified the milestones: JRuby 9.2.0.0, JRuby 9.2.1.0 May 24, 2018
@headius
Copy link
Member Author

headius commented Oct 26, 2018

This has been largely addressed by the jruby-startup gem, which combined with jruby.bash changes in JRuby 9.2.1 makes it possible to boost startup on Java 11+.

I will write up separate documentation, but see https://github.com/jruby/jruby-startup for now.

@headius headius closed this as completed Oct 26, 2018
@headius
Copy link
Member Author

headius commented Oct 26, 2018

See 960efcb and 7c4bf9c for the bash script changes. These have not been applied to the native executable for either *nix or Windows yet.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants