Slide 1

Slide 1 text

2018: The Year of JRuby Charles Oliver Nutter @headius

Slide 2

Slide 2 text

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!

Slide 3

Slide 3 text

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!

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

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!

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

Compatibility

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

Performance

Slide 15

Slide 15 text

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!

Slide 16

Slide 16 text

JRuby JVM Client (C1) Native JIT Server (C2) Native JIT Interpreter Bytecode JIT Bytecode Interpreter CPU

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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!

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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!

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

JRuby on Rails

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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!

Slide 36

Slide 36 text

No content

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

Failure: TimeWithZoneTest#test_minus_with_time_precision [activesupport/ test/core_ext/time_with_zone_test.rb:340]: Expected: 86399.999999998 Actual: 86399.99999999799

Slide 39

Slide 39 text

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()

Slide 40

Slide 40 text

Rails 5.2 Status • Tests are running pretty well • Scaffolded apps work fine • SQLite3 looks good • MySQL, PostgreSQL need updates

Slide 41

Slide 41 text

JRuby on Rails Performance

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

ActiveRecord create operations per second 0 40 80 120 160 JRuby JRuby Indy JRuby Graal CRuby 157.233 144.092 140.449 135.135

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

ActiveRecord select operations per second 0 1050 2100 3150 4200 JRuby JRuby Indy JRuby Graal CRuby 3,125 3,703 4,132 2,403

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

ActiveRecord update operations per second 0 1750 3500 5250 7000 JRuby JRuby Indy JRuby Graal CRuby 2,604 6,250 6,944 4,000

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

Measuring Rails Performance • Requests per second • ActiveRecord operations per second • CRuby versus JRuby, various configurations

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

Requests per second, full stack scaffolded read on Postgresql 0 325 650 975 1300 JRuby JDK8 Indy JRuby JDK10 Indy CRuby

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

JRuby is the fastest way to run Rails applications.

Slide 55

Slide 55 text

Summary

Slide 56

Slide 56 text

No content

Slide 57

Slide 57 text

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)

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

No content

Slide 61

Slide 61 text

Thank You! Charles Oliver Nutter [email protected] @headius http://jruby.org https://github.com/jruby/jruby