Slide 1

Slide 1 text

JRuby Startup and AOT Thomas E. Enebo (@tom_enebo) Charles O. Nutter (@headius)

Slide 2

Slide 2 text

• JRuby co-leads • Red Hat Charles Thomas Ruby Java Beer

Slide 3

Slide 3 text

What is JRuby? An implementation of the Ruby language and Runtime def ruby puts "It is pretty cool..." end

Slide 4

Slide 4 text

Why JRuby? • Native Threads • Access Java Libraries from within Ruby • The power of Java (GCs, Profiled Opts, Portability) • All the things we know and love about Java….BUT…

Slide 5

Slide 5 text

…startup time is a problem • Java is not known for short-lived command-line executions • Rubyists constantly run ruby from the command-line • They expect it to be short-lived • This is our perennial issue to solve

Slide 6

Slide 6 text

Many Ruby Core Classes % jruby -e 1 415 classes/modules defined 300+ come from Ruby source

Slide 7

Slide 7 text

Many Java Classes • More than 6400 Java classes loaded for -e 1 • Nearly 5000 are from JRuby

Slide 8

Slide 8 text

• Ruby is a dynamic expression-based language • No ahead of time knowledge of what will load Dynamic Language require 'normal' if something_special(oh_noes) require 'mystery_module' end

Slide 9

Slide 9 text

Path Searching Hell More Paths More Problems! ["/home/enebo/work/jruby/frogger/lib", "/home/enebo/work/jruby/frogger/vendor", "/home/enebo/work/jruby/frogger/app/channels", "/home/enebo/work/jruby/frogger/app/controllers", "/home/enebo/work/jruby/frogger/app/ controllers/concerns", "/home/enebo/work/jruby/frogger/app/helpers", "/home/enebo/work/jruby/frogger/app/jobs", "/home/enebo/work/jruby/frogger/app/mailers", "/home/enebo/work/jruby/frogger/app/models", "/home/enebo/ work/jruby/frogger/app/models/concerns", "/home/enebo/work/jruby/lib/ruby/gems/shared/gems/turbolinks-5.2.1/lib", "/home/enebo/work/jruby/lib/ruby/gems/shared/gems/webpacker-4.2.2/lib", "/home/enebo/work/jruby/lib/ruby/ gems/shared/gems/actiontext-6.0.2.1/lib", "/home/enebo/work/jruby/lib/ruby/gems/shared/gems/actiontext-6.0.2.1/app/helpers", "/home/enebo/work/jruby/lib/ruby/gems/shared/gems/actiontext-6.0.2.1/app/models", "/home/enebo/ work/jruby/lib/ruby/gems/shared/gems/actionmailbox-6.0.2.1/lib", "/home/enebo/work/jruby/lib/ruby/gems/shared/gems/actionmailbox-6.0.2.1/app/controllers", "/home/enebo/work/jruby/lib/ruby/gems/shared/gems/ actionmailbox-6.0.2.1/app/jobs", "/home/enebo/work/jruby/lib/ruby/gems/shared/gems/actionmailbox-6.0.2.1/app/models", "/home/enebo/work/jruby/lib/ruby/gems/shared/gems/actioncable-6.0.2.1/lib", "/home/enebo/work/jruby/lib/ ruby/gems/shared/gems/activestorage-6.0.2.1/lib", "/home/enebo/work/jruby/lib/ruby/gems/shared/gems/activestorage-6.0.2.1/app/controllers", "/home/enebo/work/jruby/lib/ruby/gems/shared/gems/activestorage-6.0.2.1/app/ controllers/concerns", "/home/enebo/work/jruby/lib/ruby/gems/shared/gems/activestorage-6.0.2.1/app/jobs", "/home/enebo/work/jruby/lib/ruby/gems/shared/gems/activestorage-6.0.2.1/app/models", "/home/enebo/work/jruby/lib/ ruby/gems/shared/gems/actionview-6.0.2.1/lib", "/home/enebo/work/jruby/lib/ruby/gems/shared/gems/bundler-2.0.1/lib", "/home/enebo/work/jruby/lib/ruby/gems/shared/gems/webdrivers-4.2.0/lib", "/home/enebo/work/jruby/lib/ruby/ gems/shared/gems/web-console-4.0.1/lib", "/home/enebo/work/jruby/lib/ruby/gems/shared/gems/tzinfo-data-1.2019.3/lib", "/home/enebo/work/jruby/lib/ruby/gems/shared/gems/turbolinks-source-5.2.0/lib", "/home/enebo/work/jruby/ lib/ruby/gems/shared/gems/selenium-webdriver-3.142.7/lib", "/home/enebo/work/jruby/lib/ruby/gems/shared/gems/sass-rails-6.0.0/lib", "/home/enebo/work/jruby/lib/ruby/gems/shared/gems/sassc-rails-2.1.2/lib", "/home/enebo/work/ jruby/lib/ruby/gems/shared/gems/tilt-2.0.10/lib", "/home/enebo/work/jruby/lib/ruby/gems/shared/gems/sassc-2.2.1/lib", "/home/enebo/work/jruby/lib/ruby/gems/shared/extensions/universal-java-13/2.5.0/sassc-2.2.1", "/home/enebo/ work/jruby/lib/ruby/gems/shared/gems/rubyzip-2.1.0/lib", "/home/enebo/work/jruby/lib/ruby/gems/shared/gems/rails-6.0.2.1/lib", "/home/enebo/work/jruby/lib/ruby/gems/shared/gems/sprockets-rails-3.2.1/lib", "/home/enebo/work/ jruby/lib/ruby/gems/shared/gems/sprockets-4.0.0/lib", "/home/enebo/work/jruby/lib/ruby/gems/shared/gems/railties-6.0.2.1/lib", "/home/enebo/work/jruby/lib/ruby/gems/shared/gems/thor-1.0.1/lib", "/home/enebo/work/jruby/lib/ruby/ gems/shared/gems/rack-proxy-0.6.5/lib", "/home/enebo/work/jruby/lib/ruby/gems/shared/gems/puma-4.3.1-java/lib", "/home/enebo/work/jruby/lib/ruby/gems/shared/gems/method_source-0.9.2/lib", "/home/enebo/work/jruby/lib/ ruby/gems/shared/gems/listen-3.1.5/lib", "/home/enebo/work/jruby/lib/ruby/gems/shared/gems/ruby_dep-1.5.0/lib", "/home/enebo/work/jruby/lib/ruby/gems/shared/gems/rb-inotify-0.10.1/lib", "/home/enebo/work/jruby/lib/ruby/gems/ shared/gems/rb-fsevent-0.10.3/lib", "/home/enebo/work/jruby/lib/ruby/gems/shared/gems/jbuilder-2.9.1/lib", "/home/enebo/work/jruby/lib/ruby/gems/shared/gems/ffi-1.12.1-java/lib", "/home/enebo/work/jruby/lib/ruby/gems/shared/ gems/childprocess-3.0.0/lib", "/home/enebo/work/jruby/lib/ruby/gems/shared/gems/capybara-3.31.0/lib", "/home/enebo/work/jruby/lib/ruby/gems/shared/gems/xpath-3.2.0/lib", "/home/enebo/work/jruby/lib/ruby/gems/shared/gems/ regexp_parser-1.6.0/lib", "/home/enebo/work/jruby/lib/ruby/gems/shared/gems/bindex-0.8.1/lib", "/home/enebo/work/jruby/lib/ruby/gems/shared/extensions/universal-java-13/2.5.0/bindex-0.8.1", "/home/enebo/work/jruby/lib/ruby/ gems/shared/gems/addressable-2.7.0/lib", "/home/enebo/work/jruby/lib/ruby/gems/shared/gems/public_suffix-4.0.3/lib", "/home/enebo/work/jruby/lib/ruby/gems/shared/gems/activerecord-jdbcsqlite3-adapter-60.1-java/lib", "/home/ enebo/work/jruby/lib/ruby/gems/shared/gems/jdbc-sqlite3-3.28.0/lib", "/home/enebo/work/jruby/lib/ruby/gems/shared/gems/activerecord-jdbc-adapter-60.1-java/lib", "/home/enebo/work/jruby/lib/ruby/gems/shared/gems/ actionmailer-6.0.2.1/lib", "/home/enebo/work/jruby/lib/ruby/gems/shared/gems/mail-2.7.1/lib", "/home/enebo/work/jruby/lib/ruby/gems/shared/gems/mini_mime-1.0.2/lib", "/home/enebo/work/jruby/lib/ruby/gems/shared/gems/ marcel-0.3.3/lib", "/home/enebo/work/jruby/lib/ruby/gems/shared/gems/mimemagic-0.3.3/lib", "/home/enebo/work/jruby/lib/ruby/gems/shared/gems/activerecord-6.0.2.1/lib", "/home/enebo/work/jruby/lib/ruby/gems/shared/gems/ activemodel-6.0.2.1/lib", "/home/enebo/work/jruby/lib/ruby/gems/shared/gems/activejob-6.0.2.1/lib", "/home/enebo/work/jruby/lib/ruby/gems/shared/gems/globalid-0.4.2/lib", "/home/enebo/work/jruby/lib/ruby/gems/shared/gems/ websocket-driver-0.7.1-java/lib", "/home/enebo/work/jruby/lib/ruby/gems/shared/gems/websocket-extensions-0.1.4/lib", "/home/enebo/work/jruby/lib/ruby/gems/shared/gems/nio4r-2.5.2-java/lib", "/home/enebo/work/jruby/lib/ruby/ gems/shared/gems/actionpack-6.0.2.1/lib", "/home/enebo/work/jruby/lib/ruby/gems/shared/gems/rack-test-1.1.0/lib", "/home/enebo/work/jruby/lib/ruby/gems/shared/gems/rack-2.1.1/lib", "/home/enebo/work/jruby/lib/ruby/gems/ shared/gems/rails-html-sanitizer-1.3.0/lib", "/home/enebo/work/jruby/lib/ruby/gems/shared/gems/loofah-2.4.0/lib", "/home/enebo/work/jruby/lib/ruby/gems/shared/gems/crass-1.0.6/lib", "/home/enebo/work/jruby/lib/ruby/gems/ shared/gems/rails-dom-testing-2.0.3/lib", "/home/enebo/work/jruby/lib/ruby/gems/shared/gems/nokogiri-1.10.7-java/lib", "/home/enebo/work/jruby/lib/ruby/gems/shared/gems/erubi-1.9.0/lib", "/home/enebo/work/jruby/lib/ruby/gems/ shared/gems/builder-3.2.4/lib", "/home/enebo/work/jruby/lib/ruby/gems/shared/gems/activesupport-6.0.2.1/lib", "/home/enebo/work/jruby/lib/ruby/gems/shared/gems/zeitwerk-2.2.2/lib", "/home/enebo/work/jruby/lib/ruby/gems/ shared/gems/tzinfo-1.2.6/lib", "/home/enebo/work/jruby/lib/ruby/gems/shared/gems/thread_safe-0.3.6-java/lib", "/home/enebo/work/jruby/lib/ruby/gems/shared/gems/minitest-5.14.0/lib", "/home/enebo/work/jruby/lib/ruby/gems/ shared/gems/i18n-1.8.2/lib", "/home/enebo/work/jruby/lib/ruby/gems/shared/gems/concurrent-ruby-1.1.5/lib", "/home/enebo/work/jruby/lib/ruby/gems/shared/gems/rake-13.0.1/lib", "/home/enebo/work/jruby/lib/ruby/2.5/site_ruby", "/ home/enebo/work/jruby/lib/ruby/stdlib"]

Slide 10

Slide 10 text

• Critical code (like Parser) takes time to warmup Everything Starts Cold Java Instructions (java bytecode) interpret C1 compile native code better native code java bytecode interpreter execute C2 compile Java Virtual Machine deoptimize

Slide 11

Slide 11 text

total execution time (lower is better) 0 0.45 0.9 1.35 1.8 -e 1 CRuby JRuby (JDK8) JRuby (10th iter) JRuby (50th iter)

Slide 12

Slide 12 text

total execution time (lower is better) 0 1.25 2.5 3.75 5 gem --version gem list (~350 gems) CRuby JRuby (JDK8) JRuby (10th iter)

Slide 13

Slide 13 text

Other Implementations • CRuby: bytecode compile and interpret • Low peak performance but fastest startup across the board • TruffleRuby: AST interpret + Futamura projection via Graal • Extremely long startup in JVM mode (10x JRuby) • Native compile base startup =~ CRuby • Apps still slow to start (2-3x JRuby)

Slide 14

Slide 14 text

A History of JRuby Startup

Slide 15

Slide 15 text

In the beginning… Circa Java 1.4 Ruby (.rb) Lexer Parser interpret interpreter keyword_def,tIDENTIFIER[foo],NL tIDENTIFIER[puts],tSTRING_BEG, tSTRING_CONTENT[hello],tSTRING_END,NL keyword_end,NL tIDENTIFIER[foo],EOF Root Method Call: Foo “foo” Call: Puts “hello” def foo puts "hello" end foo All hail the interpreted AST runtime

Slide 16

Slide 16 text

AST Runtime • Startup was ok for the time • Ruby was much simpler back then • Quick tasks were ~2-3x slower than C Ruby but very short runs • Peak performance was not good but was better than C Ruby

Slide 17

Slide 17 text

Lexer Optimization • Serialize lexer stream • Java 1.4 boost was significant (~25% faster) • Java 5 dropped improvement to <5%…death to serialization! Ruby (.rb) Lexer Parser interpret interpreter Ruby (.ser)

Slide 18

Slide 18 text

AST JIT Added • Helped keep performance edge over C Ruby 1.9 • Quick tasks startup time unaffected • Longer tasks could be if they contained hot code • This startup time of large web applications Ruby (.rb) Lexer Parser interpret interpreter Compiler Called enough To compile

Slide 19

Slide 19 text

Honorable Mention…Bytecode AOT • AOT all the things to class files! • Class verification on: insanely slow • Class verification off: slower than manually parsing Ruby • More on this technique later…

Slide 20

Slide 20 text

JRuby adds its own IR • Startup decreased 15-25% on short runs • Even better Steady State performance • Longer app startup potentially improves more Ruby (.rb) Lexer Parser IR Builder Startup Instructions Full Instructions Bytecode Generation Instructions and passes…performance for the masses

Slide 21

Slide 21 text

Lazy Building of Methods • AST tree smaller than IR build of that tree • Until a method's first call do not build it • Saves memory (%40 of Rails methods not called) • Eliminates build time (15-25%) if not called.

Slide 22

Slide 22 text

JRuby adds IR Serialization Ruby (.rb) Lexer Parser IR Builder Startup Instructions Full Instructions Bytecode Generation Ruby (.ir) Silver bullet?

Slide 23

Slide 23 text

IR Serialization • SLOWER THAN PARSING FROM SCRATCH…barely

Slide 24

Slide 24 text

Add --dev mode • Only use IR startup instructions in interpreter • Disable C2 (-XX:TieredStopAtLevel=1) • Disable bytecode verification • Most significant startup optimization to date

Slide 25

Slide 25 text

Dev Mode Flag total execution time (lower is better) 0 1.15 2.3 3.45 4.6 gem list (~350 gems) JRuby JRuby --dev

Slide 26

Slide 26 text

Native Compilers • Generate a JRuby executable • Excelsior JET and jaotc • Very slightly faster than --dev…but not quite enough • There's new developments here...

Slide 27

Slide 27 text

Startup Experiments • Preboot or reuse JVM process • Lazy deserialize and IR constant pooling • Precompile to JVM bytecode

Slide 28

Slide 28 text

Pre-booting Options • Nailgun - reuse same JVM repeatedly • Poor resource cleanup for badly-behaved apps • Drip - pre-boot next JVM in background • Troublesome TTY issues, stale instances • CRIU - checkpoint and restore in userspace • Linux only, still in proof-of-concept stage for JVM apps

Slide 29

Slide 29 text

Lazy Instruction Deserialization • All Scopes (in IR) are needed for implementation reasons • …but startup instructions are only needed it they are used IRScope Flags StaticScope Closures … Instructions Decode on demand

Slide 30

Slide 30 text

0 0.425 0.85 1.275 1.7 gem list (20 gems) 1.32 1.61 1.41 --dev old serialize + --dev lazy serialize --dev Java 13.0.2+8

Slide 31

Slide 31 text

0 1.025 2.05 3.075 4.1 gem list (2000 gems) 3.87 4.07 3.91 --dev old serialize + --dev lazy serialize --dev Java 13.0.2+8

Slide 32

Slide 32 text

0 3.575 7.15 10.725 14.3 rails console 13.7 14 14.3 --dev old serialize + --dev lazy serialize --dev Java 13.0.2+8

Slide 33

Slide 33 text

Add constant pooling • Serializer encodes/decodes a lot of the same values • Common Symbol Operand type into per IRScope constant pool • Lazily Decode pool when instructions decode • Reduces disk space • Reduces extra lookups

Slide 34

Slide 34 text

0 0.425 0.85 1.275 1.7 gem list (20 gems) 1.27 1.32 1.61 1.41 --dev old serialize + --dev lazy serialize --dev lazy + pool Java 13.0.2+8

Slide 35

Slide 35 text

0 1.025 2.05 3.075 4.1 gem list (2000 gems) 3.64 3.87 4.07 3.91 --dev old serialize + --dev lazy serialize --dev lazy + pool Java 13.0.2+8

Slide 36

Slide 36 text

0 3.575 7.15 10.725 14.3 rails console 13.6 13.7 14 14.3 --dev old serialize + --dev lazy serialize --dev lazy + pool Java 13.0.2+8

Slide 37

Slide 37 text

JVM Bytecode Compiler • Used for JIT at runtime • 50 call compile threshold • Background compiler threads • Also supports compiling entire scripts • Used for "main" script by default

Slide 38

Slide 38 text

Precompiling Goals • JVM, JRuby initialization largely unchanged • Read, parse, IR compile, IR interpret eliminated • Reduce JVM classes and IR state on heap • Reduce startup (maybe) and warmup (probably) by skipping JIT

Slide 39

Slide 39 text

$ jruby -Xjit.logging -S gem -v 2020-01-31T13:44:10.835+01:00 [main] INFO Ruby : done compiling target script: /Users/headius/projec 2020-01-31T13:44:10.981+01:00 [Ruby-0-JIT-1] INFO JITCompiler : block done jitting: Gem::Specificati 2020-01-31T13:44:10.989+01:00 [Ruby-0-JIT-1] INFO JITCompiler : method done jitting: Gem::StubSpecif 2020-01-31T13:44:10.993+01:00 [Ruby-0-JIT-1] INFO JITCompiler : method done jitting: Gem::BasicSpeci 2020-01-31T13:44:10.996+01:00 [Ruby-0-JIT-1] INFO JITCompiler : method done jitting: Gem::BasicSpeci 2020-01-31T13:44:10.998+01:00 [Ruby-0-JIT-1] INFO JITCompiler : block done jitting: Gem::Specificati 2020-01-31T13:44:11.000+01:00 [Ruby-0-JIT-1] INFO JITCompiler : block done jitting: Gem::Specificati $ jruby -Xjit.logging -X+C -S gem -v 2020-01-31T13:44:20.839+01:00 [main] INFO Ruby : done compiling target script: uri:classloader:/jrub 2020-01-31T13:44:20.861+01:00 [main] INFO Ruby : done compiling target script: uri:classloader:/jrub 2020-01-31T13:44:20.919+01:00 [main] INFO Ruby : done compiling target script: uri:classloader:/jrub 2020-01-31T13:44:20.940+01:00 [main] ERROR Ruby : failed to compile target script: uri:classloader:/ 2020-01-31T13:44:20.947+01:00 [main] INFO Ruby : done compiling target script: uri:classloader:/jrub 2020-01-31T13:44:21.008+01:00 [main] INFO Ruby : done compiling target script: file:/Users/headius/p Normal JIT mode Force compile mode

Slide 40

Slide 40 text

$ jruby -Xcompile.cache.classes -Xcompile.cache.classes.logging -X+C -S gem -v saved compiled script as /Users/headius/.ir/uri_3a_classloader_3a_/jruby/java.class saved compiled script as /Users/headius/.ir/uri_3a_classloader_3a_/jruby/java/core_ext.class saved compiled script as /Users/headius/.ir/uri_3a_classloader_3a_/jruby/java/core_ext/module.class saved compiled script as /Users/headius/.ir/uri_3a_classloader_3a_/jruby/java/java_ext.class saved compiled script as /Users/headius/.ir/Users/headius/projects/jruby/lib/jruby_dot_jar/jruby/jruby.class saved compiled script as /Users/headius/.ir/uri_3a_classloader_3a_/jruby/kernel/kernel.class saved compiled script as /Users/headius/.ir/uri_3a_classloader_3a_/jruby/kernel/process.class saved compiled script as /Users/headius/.ir/uri_3a_classloader_3a_/jruby/kernel/io.class saved compiled script as /Users/headius/.ir/uri_3a_classloader_3a_/jruby/kernel/gc.class saved compiled script as /Users/headius/.ir/uri_3a_classloader_3a_/jruby/kernel/range.class saved compiled script as /Users/headius/.ir/Users/headius/projects/jruby/lib/jruby_dot_jar/jruby/preludes.class saved compiled script as /Users/headius/.ir/uri_3a_classloader_3a_/jruby/kernel/prelude.class saved compiled script as /Users/headius/.ir/uri_3a_classloader_3a_/jruby/kernel/enc_prelude.class saved compiled script as /Users/headius/.ir/Users/headius/projects/jruby/lib/ruby/stdlib/unicode_normalize.class saved compiled script as /Users/headius/.ir/uri_3a_classloader_3a_/jruby/kernel/gem_prelude.class saved compiled script as /Users/headius/.ir/Users/headius/projects/jruby/lib/ruby/stdlib/rubygems.class saved compiled script as /Users/headius/.ir/Users/headius/projects/jruby/lib/ruby/stdlib/rbconfig.class saved compiled script as /Users/headius/.ir/uri_3a_classloader_3a_/jruby/kernel/rbconfig.class saved compiled script as /Users/headius/.ir/Users/headius/projects/jruby/lib/ruby/stdlib/rubygems/compatibility.class saved compiled script as /Users/headius/.ir/Users/headius/projects/jruby/lib/ruby/stdlib/rubygems/defaults.class saved compiled script as /Users/headius/.ir/Users/headius/projects/jruby/lib/ruby/stdlib/rubygems/errors.class saved compiled script as /Users/headius/.ir/Users/headius/projects/jruby/lib/ruby/stdlib/rubygems/version.class saved compiled script as /Users/headius/.ir/Users/headius/projects/jruby/lib/ruby/stdlib/rubygems/requirement.class saved compiled script as /Users/headius/.ir/Users/headius/projects/jruby/lib/ruby/stdlib/rubygems/platform.class saved compiled script as /Users/headius/.ir/Users/headius/projects/jruby/lib/ruby/stdlib/rubygems/basic_specification.class saved compiled script as /Users/headius/.ir/Users/headius/projects/jruby/lib/ruby/stdlib/rubygems/stub_specification.class saved compiled script as /Users/headius/.ir/Users/headius/projects/jruby/lib/ruby/stdlib/rubygems/util/list.class

Slide 41

Slide 41 text

Does it Work? • rails generate scaffold produces over 1200 class files • Cached size exceeds 80MB • At command line most of this never JITs • JVM interpreter usually slower than our interpreter • Trace bytecode execution to measure improvement

Slide 42

Slide 42 text

TraceBytecodes • Debug option -XX:+TraceBytecodes • Print out all bytecodes as executed • Use -Xint to also capture bytecodes that JIT • Claes Redestad's bytestacks: https://github.com/cl4es/bytestacks • Looking only at JVM in JIT mode • Need to quantify cold execution

Slide 43

Slide 43 text

Cold Bytecodes for -e 1 -e 1 -e 1 cached Number of bytecodes executed (cold, in millions) 0 10 20 30 40 JVM JRuby base JRuby libs Other

Slide 44

Slide 44 text

Cold bytecodes for gem list gem list gem list cached Number of bytecodes executed (cold, in millions) 0 12.5 25 37.5 50 JVM JRuby base RubyGems Other gem list

Slide 45

Slide 45 text

InvokeDynamic Usage • Normal mode: Ruby literals, constant lookups, global vars • Indy mode: method, block invocations • InvokeDynamic + MethodHandle are expensive in JVM interpreter • LambdaForms never get a chance to optimize • More bytecode than manual lookup + virtual call varargs • Enabling full indy runs 48M cold bytecodes vs 33M for "-e 1"

Slide 46

Slide 46 text

AOT Mode • AOT mode: No indy at all • A bit more bytecode generated • Only direct method handles (no LambdaForms) • (Constant lookup still using indy, will fix later) • Cold bytecodes reduced vs normal precompile

Slide 47

Slide 47 text

Cold Bytecodes for -e 1 -e 1 -e 1 cached -e 1 cached2 Number of bytecodes executed (cold, in millions) 0 10 20 30 40 JVM JRuby base JRuby libs Other

Slide 48

Slide 48 text

Cold bytecodes for gem list gem list gem list cached gem list cached2 Number of bytecodes executed (cold, in millions) 0 12.5 25 37.5 50 JVM JRuby base RubyGems Other gem list

Slide 49

Slide 49 text

Mix and Match • Experimenting with combinations • Interp, JIT, AOT modes • Serialized IR, precompiled bytecode caches • JVM options • Disabled verification and AppCDS on Hotspot • -Xquickstart and -Xshareclasses on OpenJ9

Slide 50

Slide 50 text

0 0.45 0.9 1.35 1.8 gem list (20 gems) 1.42 1.71 1.13 1.12 1.41 --dev appcds --dev lazy serialize appcds --dev classcache --dev classcache appcds --dev Java 13.0.2+8

Slide 51

Slide 51 text

0 1 2 3 4 gem list (2000 gems) 3.84 3.53 3.98 3.42 3.45 3.91 --dev appcds --dev lazy serialize appcds --dev classcache --dev classcache appcds --dev j9 quickstart+shareclasses --dev Java 13.0.2+8

Slide 52

Slide 52 text

0 4 8 12 16 rails console 11.2 12.5 15.1 12.7 13.5 14.3 --dev appcds --dev lazy serialize appcds --dev classcache --dev classcache appcds --dev partial classcache appcds --dev Java 13.0.2+8

Slide 53

Slide 53 text

Futures

Slide 54

Slide 54 text

Native Compilation • Native AOT is cool again • Time to revive GCJ! • We are experimenting with GraalVM native image

Slide 55

Slide 55 text

total execution time (lower is better) 0 0.75 1.5 2.25 3 -e 1 0.054 2.472 1.268 JRuby --dev TruffleRuby JVM TruffleRuby SVM

Slide 56

Slide 56 text

total execution time (lower is better) 0 5.5 11 16.5 22 gem --version gem list (~350 gems) JRuby --dev TruffleRuby JVM TruffleRuby SVM

Slide 57

Slide 57 text

GraalVM Native Image • Compile all of JRuby to native (working POC) • Too many limitations? • No invokedynamic, limited reflection, no dynamic classloading, ... • Ultimate goal: fully native Ruby apps (no startup or warmup) • Good for tools, microservices • Still need JVM for peak performance

Slide 58

Slide 58 text

total execution time (lower is better) 0 0.45 0.9 1.35 1.8 -e 1 0.117s 0.124s 1.63s 0.1s CRuby JRuby (JDK8) JRuby (10th iter) JRuby native

Slide 59

Slide 59 text

Native Futures • Compile Ruby app + library sources to native • Needed bytecode AOT to be working • Static optimizations • Remove unneeded parts of JRuby • Probably limited to small services, command line tools

Slide 60

Slide 60 text

Miniature Ruby subset interpreter • When file uses a restricted subset of Ruby, encode as "special miniature language" • fcalls with literals (e.g. require "date") • module • class • def (their bodies are deserialized like normal and lazy)

Slide 61

Slide 61 text

Miniature Ruby subset interpreter • More complicated Ruby uses ordinary deserialization path • Implemented a small static method to warm up quick • Will bypass creating IR directly • But IR can be lazily reconstructed

Slide 62

Slide 62 text

Miniature Ruby subset interpreter require 'date' module Package class FooDriver def run end end class BarDriver def run end end end fcall :require, 'date' create_module :Package create_class :FooDriver create_method :run, @scopeXXX return # goes back to Package create_class :BarDriver create_method :run, @scopeYYY

Slide 63

Slide 63 text

Summary • Precompiling to bytecode works but sometimes hurts startup • Class sharing features competitive with --dev without JIT limits • Lazier, lighter IR or subset languages show promise • Native compile shows promise but difficult to apply to Ruby

Slide 64

Slide 64 text

Thank You! • Charles Nutter @headius, Tom Enebo @tom_enebo • https://www.jruby.org • https://github.com/jruby/jruby • Chat with JRuby devs, users • jruby on Matrix • #jruby on Freenode IRC • Mailing list: https://lists.ruby-lang.org