Upgrade to Pro — share decks privately, control downloads, hide ads and more …

The Year of JRuby - RubyC 2018

The Year of JRuby - RubyC 2018

A status update on JRuby, covering compatibility, Rails, and next-gen performance numbers. JRuby is currently the fastest way to run Rails apps, and we're doing work to make it even faster in the future.

headius

June 02, 2018
Tweet

More Decks by headius

Other Decks in Programming

Transcript

  1. Hello! • Charles Oliver Nutter • [email protected], @headius • JVM

    language advocate at Red Hat • 20-year JVM veteran, 12 years on JRuby • Excited to be back in Kyiv!
  2. The JRuby Guys • Shout out to my buddy Tom

    Enebo • Just presented at RubyKaigi in Sendai, Japan • We've worked together on JRuby for over 12 years!
  3. Beer For You! • I am a beer lover! •

    I brought some American craft beers to give away! • Try running something on JRuby, show me, and take your pick: • Bender: oatmeal brown ale • Furious: dark American IPA • Foggy Geezer: New England IPA
  4. What is JRuby • It's just Ruby! • Ruby 2.5

    compatible, if something's broken tell us • Supports pure-Ruby gems, many extensions • We want to be a Ruby first! • It's a JVM language • Full access to the power of the JVM platform!
  5. Ruby! • JRuby 9.2 is now Ruby 2.5 compatible •

    Modulo a few things we couldn't finish • You won't notice unless you use some weird edge features • Primary focus is always on compatibility and experience • Performance comes along later
  6. JRuby 9.2 • Ruby 2.5, of course • Better support

    for non-ASCII identifiers • Method names, constants, symbols... • Keyword arguments optimizations • Preparing for new optimizations and JVMs
  7. Supporting Ruby Versions • Checklist based off CRuby's NEWS file

    • Update our copy of CRuby's tests and make them pass • Same process for ruby/spec, our own tests, and key libraries
  8. New Feature? You Can Help! • New features are great

    opportunities to contribute! • Learn more about how Ruby and JRuby work! • Help us keep up with Ruby development! • Profit! • We are always standing by on IRC, Gitter, Twitter to help you
  9. Library Compatibility • We also run library tests • Rails,

    Rake, RubyGems, ... • Core Ruby tests will never cover 100% of users • Rails in particular is a key target
  10. JRuby Architecture • Mixed-mode runtime • Interpreter runs for a

    while, gathering information • Just-in-time (JIT) compiler compiles hot code to JVM • JVM turns that into optimized native code • Heavy dependence on JVM to optimize well • Newer JVM JITs showing great promise!
  11. JRuby JVM Client (C1) Native JIT Server (C2) Native JIT

    Interpreter Bytecode JIT Bytecode Interpreter CPU
  12. Microbenching • Very fun to show off, see improve •

    Practically useless • Like judging a person by how much they can bench press • JRuby has won microbenchmarks for years, never faster on Rails • Easier to isolate specific measurements • Great for exploring new runtimes and tech
  13. bench_aref • Testing calls to ary[n] • Reduced overhead in

    JRuby 9.2 • String#[] needs to store $~ • We have to track that just in case ary is a String • JRuby 9.1 deoptimized more than necessary
  14. require 'benchmark/ips' @a = [1] def foo(a) a[0] end Benchmark.ips

    do |x| x.time = 10 x.warmup = 15 x.report("Instantiation") { a = @a i = 0; while i < 1_000_000; foo(a); i += 1; end } end
  15. bench_aref iterations per second (higher is better) 0 iter/s 10

    iter/s 20 iter/s 30 iter/s 40 iter/s JDK10 JRuby 9.1 JRuby 9.2 39.132 30.248
  16. InvokeDynamic • JVM support for dynamic invocation • Let the

    JVM see through all the dynamic bits of Ruby • Added in Java 7, with much input and testing from JRuby • Steadily improving performance, reducing overhead • -Xcompile.invokedynamic • May be default soon!
  17. bench_aref iterations per second (higher is better) 0 iter/s 22.5

    iter/s 45 iter/s 67.5 iter/s 90 iter/s JDK10 JDK10 with Indy JRuby 9.1 JRuby 9.2 JRuby 9.1 JRuby 9.2 89.169 39.132 68.122 30.248
  18. Graal • New JVM native JIT written in Java •

    Faster evolution • More advanced optimization • Plugs into JDK9+ via command line flags • Shipped with JDK10...try it today!
  19. bench_aref iterations per second (higher is better) 0 iter/s 45

    iter/s 90 iter/s 135 iter/s 180 iter/s JDK10 JDK10 with Indy JDK10 Graal with Indy JRuby 9.1 JRuby 9.2 JRuby 9.1 JRuby 9.2 JRuby 9.1 JRuby 9.2 172.541 89.169 39.132 100.401 68.122 30.248
  20. bench_mandelbrot • Generate a text Mandelbrot fractal • See? Useful!

    • Test of numeric performance • Heavy reliance on JVM to optimize • Graal is especially good to us here
  21. bench_mandelbrot.rb def mandelbrot(size) sum = 0 byte_acc = 0 bit_num

    = 0 y = 0 while y < size ci = (2.0*y/size)-1.0 x = 0 while x < size zrzr = zr = 0.0 zizi = zi = 0.0 cr = (2.0*x/size)-1.5 escape = 0b1 z = 0 while z < 50
  22. bench_mandelbrot total execution time (lower is better) 0s 1s 2s

    3s 4s CRuby 2.5 CRuby 2.6 JIT JRuby JRuby Indy JRuby Indy Graal 0.139s 1.33s 2.95s 3.5s 3.57s
  23. bench_mandelbrot total execution time (lower is better) 0s 0.036s 0.071s

    0.107s 0.142s JRuby Indy Graal TruffleRuby 0.142s 0.139s
  24. bench_red_black • Pure-Ruby red/black tree implementation • Good for comparing

    object-heavy, indirection-heavy code • Lots of blocks, instance variable accesses • Benchmark creates tree, searches it, modifies it, clears it
  25. bench_red_black.rb def rbt_bm n = 100_000 a1 = []; n.times

    { a1 << rand(999_999) } a2 = []; n.times { a2 << rand(999_999) } start = Time.now tree = RedBlackTree.new n.times {|i| tree.add(i) } n.times { tree.delete(tree.root) } tree = RedBlackTree.new a1.each {|e| tree.add(e) } a2.each {|e| tree.search(e) } tree.inorder_walk {|key| key + 1 } tree.reverse_inorder_walk {|key| key + 1 } n.times { tree.minimum } n.times { tree.maximum } return Time.now - start end
  26. bench_red_black total execution time (lower is better) 0s 0.325s 0.65s

    0.975s 1.3s CRuby 2.5 CRuby 2.6 JIT JRuby JRuby Indy JRuby Indy Graal 0.526s 0.431s 1.171s 1.143s 1.222s
  27. A Long, Hard Journey • JRuby first ran Rails in

    2006 • Almost as long as Rails has existed! • Thousands of JRoR instances around the world • JRuby 9000, Ruby 2.4, 2.5 work slowed down Rails support • Rails 5.0 not supported for at least a year • ActiveRecord suffered the most
  28. ActiveRecord JDBC • ActiveRecord atop Java DataBase Connectivity API •

    No C extensions, no -devel packages, no headers or compiling • Rebooted last year to reduce maintenance hassle • 1:1 match with Rails versions (e.g. Rails 5.1 = ARJDBC 51.x) • SQLite3, MySQL/Maria, PostgreSQL • Sharing 90%+ of code with Rails
  29. Example: MySQL support • Old adapter: 1600+ lines of Ruby

    code • 50.0 adapter: 284 lines • More can be removed, moved into Rails • Reduced Java/JDBC code as well • 51.0 adapter: only 6 lines had to change!
  30. Rails 5.1.6 actioncable: 139 runs, 733 assertions, 10 failures, 2

    errors actionpack: 3063 runs, 14947 assertions, 2 failures, 0 errors actionmailer: 204 runs, 456 assertions, 0 failures, 0 errors actionview: 1957 runs, 4303 assertions, 3 failures, 4 errors activejob: 137 runs, 302 assertions, 0 failures, 0 errors activemodel: 713 runs, 2017 assertions, 0 failures, 0 errors activerecord: 4991 runs, 13902 assertions, 0 failures, 0 errors activesupport: 3671 runs, 760486 assertions, 14 failures, 0 errors railties: 40 runs, 73 assertions, 0 failures, 1 errors 99.995% pass
  31. Rails 5.2.0 actioncable: something broken bootstrapping actionpack: 3148 runs, 15832

    assertions, 1 failures, 0 errors actionmailer: 204 runs, 457 assertions, 0 failures, 0 errors actionview: 1990 runs, 4395 assertions, 4 failures, 4 errors activejob: 173 runs, 401 assertions, 0 failures, 0 errors activemodel: 803 runs, 2231 assertions, 0 failures, 0 errors activerecord: 5226 runs, 14665 assertions, 8 failures, 6 errors activesupport: 4135 runs, 762864 assertions, 17 failures, 2 errors railties: uses fork()
  32. Rails 5.2 Status • Tests are running pretty well •

    Scaffolded apps work fine • SQLite3 looks good • MySQL, PostgreSQL need updates
  33. ActiveRecord Performance • Rails apps live and die by ActiveRecord

    • Largest CPU consumer by far • Heavy object churn, GC overhead • Create, read, and update measurements • If delete is your bottleneck, we need to talk • CRuby 2.5.1 vs JRuby 9.2 on JDK10
  34. ActiveRecord create operations per second 0 40 80 120 160

    JRuby JRuby Indy JRuby Graal CRuby 157.233 144.092 140.449 135.135
  35. ActiveRecord find(id) operations per second 0 1250 2500 3750 5000

    JRuby JRuby Indy JRuby Graal CRuby 3,940 4,672 4,999 3,937
  36. ActiveRecord select operations per second 0 1050 2100 3150 4200

    JRuby JRuby Indy JRuby Graal CRuby 3,125 3,703 4,132 2,403
  37. ActiveRecord find_all operations per second 0 525 1050 1575 2100

    JRuby JRuby Indy JRuby Graal CRuby 1,597 2,016 1,908 1,677
  38. ActiveRecord update operations per second 0 1750 3500 5250 7000

    JRuby JRuby Indy JRuby Graal CRuby 2,604 6,250 6,944 4,000
  39. Scaling Rails • Classic problem on MRI • No concurrent

    threads, so we need processes • Processes inevitably duplicate runtime state • Much effort and lots of money wasted • JRuby is a great answer! • Multi-threaded single process runs your whole site
  40. Measuring Rails Performance • Requests per second • ActiveRecord operations

    per second • CRuby versus JRuby, various configurations
  41. Requests Per Second • Rails 5.1.6, Postgresql 10, scaffolded view

    • 4k requests to warm up, then measure every 10k • EC2 c4.xlarge: 4 vCPUs, 7.5GB • Bench, database, and app on same instance
  42. Requests per second, full stack scaffolded read on Postgresql 0

    325 650 975 1300 JRuby JDK8 Indy JRuby JDK10 Indy CRuby
  43. Requests per second 0 325 650 975 1300 Requests over

    time 10k 20k 30k 40k 50k 60k 70k 80k 90k 100k CRuby 2.5 JRuby JDK8 JRuby JDK8 Indy JRuby JDK10 JRuby JDK10 Indy JRuby JDK10 Graal Indy CRuby 2.6 JIT
  44. JRuby on Rails Memory • Single instance is much bigger,

    400-500MB versus 50MB • Ten CRuby processes = 500MB • Ten JRuby threads = 400-500MB • May need to tell JVM a memory cap • For 100-way or 1000-way...you do the math
  45. JRuby Flags • -Xcompile.invokedynamic • Enable the use of JVM's

    InvokeDynamic feature • Faster straight-line perf, maybe slower startup/warmup • -Xfixnum.cache=false • Disable caching -256..256 Fixnum objects to help Graal • --dev for improved startup time (disables optimization)
  46. Getting Graal • Download JDK10, it's in there • -XX:+UnlockExperimentalVMOptions


    -XX:+EnableJVMCI
 -XX:+UseJVMCICompiler • Put in JAVA_OPTS or prefix with -J at JRuby CLI • Download "GraalVM" as your JDK • Commercial product, but maybe that's your thing
  47. JDK10 Warnings • JDK9 introduced stricter encapsulation • We poke

    through that encapsulation to support Ruby features • You'll see warnings...they're harmless, but we'll deal with them