jruby on graal - the graal workshop · jruby review • ruby for the jvm • two-way integration...

39
JRuby on Graal Performance and Startup Experiments

Upload: others

Post on 02-Aug-2020

4 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: JRuby on Graal - The Graal Workshop · JRuby Review • Ruby for the JVM • Two-way integration with Java, fitting into ecosystem • We are a Ruby implementation, but also a JVM

JRuby on GraalPerformance and Startup Experiments

Page 2: JRuby on Graal - The Graal Workshop · JRuby Review • Ruby for the JVM • Two-way integration with Java, fitting into ecosystem • We are a Ruby implementation, but also a JVM

JRuby Review• Ruby for the JVM

• Two-way integration with Java, fitting into ecosystem

• We are a Ruby implementation, but also a JVM language

• Core classes largely written in Java

• Parts of core and most of standard library in Ruby

• Distribution like CRuby or as jars/wars, embedded into apps

• No support for CRuby extensions, on purpose

Page 3: JRuby on Graal - The Graal Workshop · JRuby Review • Ruby for the JVM • Two-way integration with Java, fitting into ecosystem • We are a Ruby implementation, but also a JVM

JRuby Architecture

Ruby (.rb)

JIT

Java Instructions (java bytecode)

Ruby Instructions (IR)

parse

interpret

interpreter

interpret

C1 c

ompi

le

native code better native code

java bytecode interpreter

execute

C2 c

ompi

le

Java Virtual MachineJRuby Internals

deoptimize

Page 4: JRuby on Graal - The Graal Workshop · JRuby Review • Ruby for the JVM • Two-way integration with Java, fitting into ecosystem • We are a Ruby implementation, but also a JVM

JRuby Challenges• Java bytecode is a narrow vocabulary

• InvokeDynamic helps but adds complexity

• Object boxes are too expensive

• Lambda-style code optimizes poorly

• Startup time, memory footprint are crucial for adoption

• Two FTEs barely keeps up with compatibility, user issues

Page 5: JRuby on Graal - The Graal Workshop · JRuby Review • Ruby for the JVM • Two-way integration with Java, fitting into ecosystem • We are a Ruby implementation, but also a JVM
Page 6: JRuby on Graal - The Graal Workshop · JRuby Review • Ruby for the JVM • Two-way integration with Java, fitting into ecosystem • We are a Ruby implementation, but also a JVM

JRuby and Graal

Page 7: JRuby on Graal - The Graal Workshop · JRuby Review • Ruby for the JVM • Two-way integration with Java, fitting into ecosystem • We are a Ruby implementation, but also a JVM

History• Experimented with Maxine back in the day

• Collaborated with TruffleRuby early on

• Investigating JRuby performance on Graal

• Playing with compiler passes

• Studying compiler IR, assembly code for opportunities

Page 8: JRuby on Graal - The Graal Workshop · JRuby Review • Ruby for the JVM • Two-way integration with Java, fitting into ecosystem • We are a Ruby implementation, but also a JVM

Today• JRuby on Graal straight-line performance

• Microbenchmarks up to small web services

• JRuby native with GraalVM

• Working POC

• Plans going forward

Page 9: JRuby on Graal - The Graal Workshop · JRuby Review • Ruby for the JVM • Two-way integration with Java, fitting into ecosystem • We are a Ruby implementation, but also a JVM

Performance

Page 10: JRuby on Graal - The Graal Workshop · JRuby Review • Ruby for the JVM • Two-way integration with Java, fitting into ecosystem • We are a Ruby implementation, but also a JVM

It's a Hard Problem• Heavy use of invokedynamic

• Method calls, constants, globals, instance variables, ...

• Limited specialization

• Object shaping, flattened arrays, frame elimination, splitting

• Looking for new opportunities

• e.g. "truly final" final fields

Page 11: JRuby on Graal - The Graal Workshop · JRuby Review • Ruby for the JVM • Two-way integration with Java, fitting into ecosystem • We are a Ruby implementation, but also a JVM

General Notes• Java 8, Java 13, GraalVM 20

• Invokedynamic, fixnum caching options

• Java 13 using -XX:+UseParallelGC

• Iterations or requests per second (higher is better)

• Force compilation to JVM bytecode (no interpreted phase)

Page 12: JRuby on Graal - The Graal Workshop · JRuby Review • Ruby for the JVM • Two-way integration with Java, fitting into ecosystem • We are a Ruby implementation, but also a JVM

Integer Loop

• Simple while loop from zero to 10M

• "nanobenchmark"

• Small method, simple integer math, conditional looping

Page 13: JRuby on Graal - The Graal Workshop · JRuby Review • Ruby for the JVM • Two-way integration with Java, fitting into ecosystem • We are a Ruby implementation, but also a JVM

Integer Loop

0

6

12

18

24

Iterations per second

22.520.3

18

12.5

Java 8 Java 8 + Indy Java 13 + Indy Graal 20 + Indy

Page 14: JRuby on Graal - The Graal Workshop · JRuby Review • Ruby for the JVM • Two-way integration with Java, fitting into ecosystem • We are a Ruby implementation, but also a JVM

Helping Graal• Make more state final

• Fewer loads, more constant propagation

• Avoid caching elidable objects

• Mixing real and virtual objects seems to cause problems

• Added a flag to disable Fixnum cache (like Integer.valueOf)

Page 15: JRuby on Graal - The Graal Workshop · JRuby Review • Ruby for the JVM • Two-way integration with Java, fitting into ecosystem • We are a Ruby implementation, but also a JVM

Integer Loop

0

17.5

35

52.5

70

Iterations per second

67.1

22.520.31812.5

Java 8 Java 8 + Indy Java 13 + Indy Graal 20 + IndyGraal 20 + Indy - Fixnums

Page 16: JRuby on Graal - The Graal Workshop · JRuby Review • Ruby for the JVM • Two-way integration with Java, fitting into ecosystem • We are a Ruby implementation, but also a JVM

Mandelbrot• Microbenchmark: one moderately-sized method

• Nearly all numeric computation

• Reasonable baseline for numeric algorithm performance

• Worst case for JRuby on most JVMs

• 100% boxed numerics

• Allocation rather than GC is the bottleneck

Page 17: JRuby on Graal - The Graal Workshop · JRuby Review • Ruby for the JVM • Two-way integration with Java, fitting into ecosystem • We are a Ruby implementation, but also a JVM

Mandelbrot Optimizations

• Final references to Boolean objects, core classes

• Keep literal numerics as primitives

• Avoid caching Fixnum objects

Page 18: JRuby on Graal - The Graal Workshop · JRuby Review • Ruby for the JVM • Two-way integration with Java, fitting into ecosystem • We are a Ruby implementation, but also a JVM

0

0.5

1

1.5

2

Java 8 Java 8 + Indy Java 13 + Indy Ruby 2.6.5

Page 19: JRuby on Graal - The Graal Workshop · JRuby Review • Ruby for the JVM • Two-way integration with Java, fitting into ecosystem • We are a Ruby implementation, but also a JVM

0

3.5

7

10.5

14

Java 8 Java 8 + Indy Java 13 + Indy Graal 20 + IndyGraal 20 + Indy - Fixnums

Page 20: JRuby on Graal - The Graal Workshop · JRuby Review • Ruby for the JVM • Two-way integration with Java, fitting into ecosystem • We are a Ruby implementation, but also a JVM

Optcarrot

• Nintendo Entertainment System emulator in pure Ruby

• Heavy use of simulated memory (integer arrays), dynamic dispatch

• Very little optimization work on JRuby side

Page 21: JRuby on Graal - The Graal Workshop · JRuby Review • Ruby for the JVM • Two-way integration with Java, fitting into ecosystem • We are a Ruby implementation, but also a JVM

OptcarrotFirst 100 iterations

0

17.5

35

52.5

70

JRuby Java 13 JRuby Graal 20 Ruby 2.6.5 JIT

Page 22: JRuby on Graal - The Graal Workshop · JRuby Review • Ruby for the JVM • Two-way integration with Java, fitting into ecosystem • We are a Ruby implementation, but also a JVM

OptcarrotLast 100 iterations

0

22.5

45

67.5

90

JRuby Java 13 JRuby Graal 20 Ruby 2.6.5 JIT

Page 23: JRuby on Graal - The Graal Workshop · JRuby Review • Ruby for the JVM • Two-way integration with Java, fitting into ecosystem • We are a Ruby implementation, but also a JVM

Applications• Roda

• Microservice-style web framework

• Rails

• Heavily dependent on ActiveRecord performance

• CRuby vs JRuby, JRuby + Graal

Page 24: JRuby on Graal - The Graal Workshop · JRuby Review • Ruby for the JVM • Two-way integration with Java, fitting into ecosystem • We are a Ruby implementation, but also a JVM

Roda• Small, well-supported service framework

• Many production users at large scales

• Very simple example with no database

• Benchmarking request routing mostly

• Good indicator of small app performance

Page 25: JRuby on Graal - The Graal Workshop · JRuby Review • Ruby for the JVM • Two-way integration with Java, fitting into ecosystem • We are a Ruby implementation, but also a JVM

Setup

• CRuby: 8 processes

• JRuby: 8 threads

• Driver: wrk with 16 connections, 2 reactor threads

Page 26: JRuby on Graal - The Graal Workshop · JRuby Review • Ruby for the JVM • Two-way integration with Java, fitting into ecosystem • We are a Ruby implementation, but also a JVM

Roda Full Concurrency

0

12500

25000

37500

50000

22,778

41,034 38,820 38,646 37,609 38,02242,304 41,731 39,172 41,786

15,556 15,275 14,533 12,872 14,093 13,593 14,141 14,764 12,236 13,998

CRuby JRuby Java 13 JRuby Graal 20 JRuby Java 13 Graal

Page 27: JRuby on Graal - The Graal Workshop · JRuby Review • Ruby for the JVM • Two-way integration with Java, fitting into ecosystem • We are a Ruby implementation, but also a JVM

ActiveRecord Performance• Rails apps live and die by ActiveRecord

• Largest CPU consumer by far

• Heavy object churn, GC overhead

• Create, read, and update measurements

• CRuby 2.5.1 vs JRuby 9.2 on JDK11

Page 28: JRuby on Graal - The Graal Workshop · JRuby Review • Ruby for the JVM • Two-way integration with Java, fitting into ecosystem • We are a Ruby implementation, but also a JVM

ActiveRecord Selects

time for 1000 selects, lower is better

0

0.075

0.15

0.225

0.3

CRuby 2.5 JRuby C2 JRuby Graal

binary boolean date datetime decimal float integerstring text time timestamp *

Page 29: JRuby on Graal - The Graal Workshop · JRuby Review • Ruby for the JVM • Two-way integration with Java, fitting into ecosystem • We are a Ruby implementation, but also a JVM

JRuby + Graal• Clear wins for small, object-heavy benchmarks

• Larger applications are a mixed bag

• Need to dig deeper and see why

• Potential to be the fastest way to run JRuby

• Applicable to other languages and libraries on JVM

Page 30: JRuby on Graal - The Graal Workshop · JRuby Review • Ruby for the JVM • Two-way integration with Java, fitting into ecosystem • We are a Ruby implementation, but also a JVM

Startup

Page 31: JRuby on Graal - The Graal Workshop · JRuby Review • Ruby for the JVM • Two-way integration with Java, fitting into ecosystem • We are a Ruby implementation, but also a JVM

JRuby Architecture

Ruby (.rb)

JIT

Java Instructions (java bytecode)

Ruby Instructions (IR)

parse

interpret

interpreter

interpret

C1 c

ompi

le

native code better native code

java bytecode interpreter

execute

C2 c

ompi

le

Java Virtual MachineJRuby Internals

deoptimize

Page 32: JRuby on Graal - The Graal Workshop · JRuby Review • Ruby for the JVM • Two-way integration with Java, fitting into ecosystem • We are a Ruby implementation, but also a JVM

total execution time (lower is better)

0

0.45

0.9

1.35

1.8

-e 1

0.0750.124

1.63

0.1

CRuby JRuby (JDK8) JRuby (10th iter) JRuby (50th iter)

Page 33: JRuby on Graal - The Graal Workshop · JRuby Review • Ruby for the JVM • Two-way integration with Java, fitting into ecosystem • We are a Ruby implementation, but also a JVM

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)

Page 34: JRuby on Graal - The Graal Workshop · JRuby Review • Ruby for the JVM • Two-way integration with Java, fitting into ecosystem • We are a Ruby implementation, but also a JVM

Startup Experiments

• Preboot or reuse JVM process

• Save parse results, compiled IR

• Precompile to native

Page 35: JRuby on Graal - The Graal Workshop · JRuby Review • Ruby for the JVM • Two-way integration with Java, fitting into ecosystem • We are a Ruby implementation, but also a JVM

GraalVM Native Image• Compile all of JRuby to native (working POC)

• Build times in 2-3min range... not bad

• Many limitations

• No invokedynamic, limited reflection, no dynamic classloading, ...

• Eventual goal: fully native Ruby apps (no startup or warmup)

• Compile Ruby to bytecode, and then to native

• Good for tools, microservices

Page 36: JRuby on Graal - The Graal Workshop · JRuby Review • Ruby for the JVM • Two-way integration with Java, fitting into ecosystem • We are a Ruby implementation, but also a JVM

total execution time (lower is better)

0

0.45

0.9

1.35

1.8

-e 1

0.117s

1.63s

0.1s

CRuby JRuby (JDK8) JRuby native

Page 37: JRuby on Graal - The Graal Workshop · JRuby Review • Ruby for the JVM • Two-way integration with Java, fitting into ecosystem • We are a Ruby implementation, but also a JVM

Bytecode AOT Mode

• AOT mode: No indy at all

• A bit more bytecode generated

• Only direct method handles or LambdaMetaFactory objects

• Cold bytecodes reduced vs normal precompile

Page 38: JRuby on Graal - The Graal Workshop · JRuby Review • Ruby for the JVM • Two-way integration with Java, fitting into ecosystem • We are a Ruby implementation, but also a JVM

Next Steps• Compile Ruby app + library sources to native

• Needed bytecode AOT to proceed

• Static optimizations

• Remove unneeded parts of JRuby

• Probably limited to small services, command line tools

• libjruby?

Page 39: JRuby on Graal - The Graal Workshop · JRuby Review • Ruby for the JVM • Two-way integration with Java, fitting into ecosystem • We are a Ruby implementation, but also a JVM

Thank you!

• Charles Oliver Nutter

• @headius, [email protected]

• blog.headius.com