Slide 1

Slide 1 text

Bringing Ruby to the JVM Making the Impossible Possible

Slide 2

Slide 2 text

About me • Charles Oliver Nutter (@headius) • Born and raised in Minnesota • Professional developer since 1996 • Currently at Red Hat (IBM) • Programming languages and natural languages

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

40 Years of Programming • Early 80s: Atari 400 (Atari BASIC), IBM PC XT (IBM BASIC) • Late 80s: K&R C, some x86 assembly • Mid-90s: Visual Basic, Delphi, C++ for Win32 development • 1995: AppleScript and Perl for University of Minnesota webapps • 1996: Java starting with v1.0.2, applets mostly

Slide 8

Slide 8 text

Languages are Tools • Good developers can get work done using any language • Good languages encourage good developers • You need to know your tools and know them well • You need more than one tool

Slide 9

Slide 9 text

Java Finds Its Way • Java before 1.3 was too slow, too big for desktop or scripting • Long-running server applications fi t better • 1996-1998: U of MN and IBM collaboration on "Student Server" • Predated most app server tech we know today • Taught me how overcomplicated software can be • 1999-2006: Java Web and Enterprise Application Architect

Slide 10

Slide 10 text

No content

Slide 11

Slide 11 text

Ruby • Born the same year as Java (1995) • In fl uences: Perl, Lisp, Smalltalk, Python, POSIX and C • Heavily dynamic: method calls, fi elds, constants, class structure • Prettier than Perl, less arcane than LISP, designed for happiness

Slide 12

Slide 12 text

# The Greeter class class Greeter def initialize(name) @name = name.capitalize end def salute puts "Hello #{@name}!" end end # Create a new object g = Greeter.new("world") # Output "Hello World!" g.salute

Slide 13

Slide 13 text

# The Greeter class class Greeter def initialize(name) @name = name.capitalize end def salute puts "Hello #{@name}!" end end # Create a new object g = Greeter.new("world") # Output "Hello World!" g.salute # The Greeter class public class Greeter { public Greeter(String name) { this.name = name.capitalize(); } public void salute() System.out.println("Hello " + name + "!"); end end # Create a new object Greeter g = new Greeter("world"); # Output "Hello World!" g.salute();

Slide 14

Slide 14 text

# The Greeter class class Greeter def initialize(name) @name = name.capitalize end def salute puts "Hello #{@name}!" end end # Create a new object g = Greeter.new("world") # Output "Hello World!" g.salute # The Greeter class public class Greeter { public Greeter(String name) { this.name = name.capitalize(); } public void salute() System.out.println("Hello " + name + "!"); end end # Create a new object Greeter g = new Greeter("world"); # Output "Hello World!" g.salute();

Slide 15

Slide 15 text

RubyConf 2004 • My fi rst Ruby conference • Koichi Sasada presented Yet Another Ruby VM (YARV) • Largely the same VM in CRuby today • Hallway hacking produced early versions of key Ruby tools • A total of 65 attendees present

Slide 16

Slide 16 text

No content

Slide 17

Slide 17 text

Ruby Finds Its Way • Ruby was for "scripting", but Python, Perl were still more popular • 2004: Ruby on Rails announced • 2005: Rails takes off, changing web development forever • Convention over con fi guration: instant applications • Many other frameworks started to pay attention

Slide 18

Slide 18 text

Can I use Ruby? • Fully committed to the Java ecosystem • Huge corpus of libraries and tools • Hotspot, JRockit JITs solved performance issues • Parallel, concurrent GCs improved memory overhead • What if we could make a sort of "J-Ruby" on the JVM?

Slide 19

Slide 19 text

No content

Slide 20

Slide 20 text

JRuby is (Re)born • 2001-2004: Early work on JRuby • Jan Arne Petersen started by porting the parser • Basic core classes added, simple scripting use cases • By 2004, only a few active developers working • Most active was Tom Enebo, my U of MN co-worker! • I rolled up my sleeves and got to work

Slide 21

Slide 21 text

JRuby Finds Its Way • Ruby on Rails sparked sudden interest in Ruby web apps • Java platform is great for web app hosting • The obvious path: JRuby on Rails • Could it be done? • Should it be done?

Slide 22

Slide 22 text

The Road to Rails • Almost nothing worked in JRuby ca. 2005 • We started with basic tools and fi lled in the blanks • Minimal compliance tests available so we ran everything we could • And we quickly ran into challenges adapting Ruby to JVM • Now the fun starts 😁

Slide 23

Slide 23 text

IRB: Interactive Ruby • First step: run the standard REPL • Heavy work on language compatibility • "eval" behavior: parse and run code • Console behavior: stdio and readline • And then one day in early 2006, it worked! $ irb irb(main):001:0> 1 + 2 => 3 irb(main):002:1* class Foo irb(main):003:2* def foo irb(main):004:2* puts 1 irb(main):005:1* end irb(main):006:0> end => :foo irb(main):007:0> Foo.new.foo 1 => nil

Slide 24

Slide 24 text

Rake and RubyGems • After RubyConf 2004 and 2005, tools started to solidify • Rake for build tooling (Ruby's equivalent to make) • File IO, fi lesystem manipulation needed lots of work • RubyGems for reusable packaging, dependency management • Network IO for fetching, zlib/tar for archives, YAML for descriptors

Slide 25

Slide 25 text

But it's JRuby • Ruby and Java must be able to call each other • Primary goal: make Java classes, methods feel like Ruby • Stretch goal: implement JRuby and libraries in Ruby calling Java • Best Java integration experience of any dynamic languge

Slide 26

Slide 26 text

Ruby/Java Integration (JI) • Each Java class gets a mirror Ruby class • With full mutability: add methods and Ruby code will see them • Java objects wrapped in an IRubyObject wrapper • Numerics and strings auto-convert across the boundary • Common idioms made Ruby-like: x.setFoo(...) becomes x.foo = ... • Interface impl and class extension

Slide 27

Slide 27 text

JRuby IRB

Slide 28

Slide 28 text

Putting It All Together • Spring 2006: JRuby on Rails routes its fi rst request • It was so slow! 😂 • ...because we were still in development mode! 🤦 • JavaOne 2006: JRuby on Rails presented for the fi rst time • Sun Microsystems, creator of Java, brings us on board • Full-time work on JRuby!

Slide 29

Slide 29 text

Reimplementing the World • Quickly became obvious how different Java and Ruby are • String and IO handling • Dynamic everything • Heavy native library use • One by one we reimplemented key parts of Java for JRuby

Slide 30

Slide 30 text

Regular Expressions • Java's Regex crashes on large alternations (think /a*|b*/) • Like one use in Rails to parse image binaries 😬 • We tried every Java regex engine we could fi nd • JRegex was the best at the time • Eventually a user would port CRuby's regex engine...

Slide 31

Slide 31 text

$ jruby -w -e 'java.util.regex.Pattern.matches("(a|b)*", "a" * 10000 + "b" * 10000)' Error: Your application used more stack memory than the safety cap of 2048K. Specify -J-Xss####k to increase it (#### = cap size in KB). java.lang.StackOverflowError at java.util.regex.Pattern$Loop.match(java/util/regex/Pattern.java:5074) at java.util.regex.Pattern$GroupTail.match(java/util/regex/Pattern.java:5000) at java.util.regex.Pattern$BranchConn.match(java/util/regex/Pattern.java:4878) at java.util.regex.Pattern$BmpCharProperty.match(java/util/regex/Pattern.java:4134) at java.util.regex.Pattern$Branch.match(java/util/regex/Pattern.java:4914) at java.util.regex.Pattern$GroupHead.match(java/util/regex/Pattern.java:4969) at java.util.regex.Pattern$Loop.match(java/util/regex/Pattern.java:5078) at java.util.regex.Pattern$GroupTail.match(java/util/regex/Pattern.java:5000) at java.util.regex.Pattern$BranchConn.match(java/util/regex/Pattern.java:4878) at java.util.regex.Pattern$BmpCharProperty.match(java/util/regex/Pattern.java:4134) at java.util.regex.Pattern$Branch.match(java/util/regex/Pattern.java:4914) at java.util.regex.Pattern$GroupHead.match(java/util/regex/Pattern.java:4969) at java.util.regex.Pattern$Loop.match(java/util/regex/Pattern.java:5078) at java.util.regex.Pattern$GroupTail.match(java/util/regex/Pattern.java:5000) at java.util.regex.Pattern$BranchConn.match(java/util/regex/Pattern.java:4878) at java.util.regex.Pattern$BmpCharProperty.match(java/util/regex/Pattern.java:4134) ...

Slide 32

Slide 32 text

Joni • Java Oniguruma ported from C by Marcin Mielżyński • Bytecode-based register machine, no stack issues • byte[] matching (also ported to char[] for Nashorn) • Pluggable character encodings, regex grammars • https://github.com/jruby/joni

Slide 33

Slide 33 text

A String is not just a String • Ruby's String is a byte[] plus an Encoding • ...and mutable by default • Multiple differently-encoded strings in memory • "Binary" is considered an encoding (ASCII-8BIT) • Emulating this with java.lang.String was impossible • Implemented our own String and Encoding logic 🤯

Slide 34

Slide 34 text

JCodings • byte[]-based character encoding and transcoding framework • Ported from CRuby's own "multilingualization" backend (M17N) • All major encodings supported, plus most weird ones • I have not ported EBCDIC logic yet... • https://github.com/jruby/jcodings

Slide 35

Slide 35 text

Secure Sockets • SSL in CRuby provided by OpenSSL • Large, complicated wrapper extension • JRuby-OpenSSL was needed to bridge the gap • Ola Bini worked tirelessly on "OpenSSL for JVM" • Still in use today (we need help updating or replacing it!)

Slide 36

Slide 36 text

Native Interop • CRuby's extension API is too invasive, exposes VM guts • Biggest thing holding back CRuby VM development • JRuby introduces the Java Native Runtime (JNR) • Tools for binding and calling native libraries • https://github.com/jnr • Later, ported Ruby FFI from Rubinius for JRuby and CRuby

Slide 37

Slide 37 text

Ruby FFI class Timeval < FFI::Struct layout :tv_sec => :ulong, :tv_usec => :ulong end module LibC extend FFI::Library ffi_lib FFI::Library::LIBC attach_function :gettimeofday, [ :pointer, :pointer ], :int end t = Timeval.new LibC.gettimeofday(t.pointer, nil)

Slide 38

Slide 38 text

Project Panama • Foreign function API • With JIT help to make direct calls • Foreign memory API • JVM-assisted access, lifecycle • API extraction from C/++ headers • Save time setting up bindings

Slide 39

Slide 39 text

//point.h struct Point2d { double x; double y; }; double distance(struct Point2d); jextract --source -t org.jextract point.h import java.lang.foreign.*; import static org.jextract.point_h.*; import org.jextract.Point2d; class TestPoint { public static void main(String[] args) { try (var session = MemorySession.openCon fi ned()) { MemorySegment point = MemorySegment.allocateNative(Point2d.$LAYOUT(), session); Point2d.x$set(point, 3d); Point2d.y$set(point, 4d); distance(point); } } }

Slide 40

Slide 40 text

JIT Compiler • Interpretation worked, but was much too slow • All state on heap, no inlining of code, JVM could not optimize • 2007-2008: fi rst ever bytecode JIT on JVM (as far as I know) • Which also made this the fi rst native JIT for Ruby • After N calls, translate AST to bytecode • JVM picks it up and does its thing

Slide 41

Slide 41 text

Catch up, JVM! • More and more of JRuby leaving standard Java behind • New string/regexp, new IO, native calls, bytecode JIT • 2007: InvokeDynamic is born • Bytecode and API for teaching the JVM new tricks • Dynamic call sites + method pointers + adapters • JRuby integrates "indy" immediately, helps design features

Slide 42

Slide 42 text

InvokeDynamic Performance Times faster than JRuby Java 8 no indy 0 1.25 2.5 3.75 5 Mandelbrot Red/Black 4.05x 3.92x 3.74x 3.68x 3.72x 1.97x Java 8 indy Java 11 indy Java 17 indy 3.28x CRuby JIT 1.86x CRuby JIT

Slide 43

Slide 43 text

Indy + Graal JIT? Times faster than JRuby Java 8 no indy 0 4 8 12 16 Mandelbrot Red/Black 3.13x 15.7x 4.05x 3.92x 3.74x 3.68x 3.72x 1.97x Java 8 indy Java 11 indy Java 17 indy Graal CE indy Escape analysis But not always better

Slide 44

Slide 44 text

New Compiler Design • 2009-2015: Subramaniam Sastry helps us write a new compiler • Background in C++ optimizing compilers • New Intermediate Representation (IR) based on traditional design • Basic blocks, operands, control and data fl ow analysis • Register machine rather than all stack • Bytecode JIT got simpler, IR did most of the work

Slide 45

Slide 45 text

Virtual Threads • 2007: Ruby introduces Fiber • Single native thread drives several fi bers • Context switch is voluntary and explicit • Thousands of microthreads potentially • No fi bers on JVM, so we used native threads 😕 • Big, heavy, slow, and can't run very many

Slide 46

Slide 46 text

Thread 2 Fiber 2 Fiber 1 hands o ff to Fiber 2, which runs immediately etc Execution fl ow Fiber 1 Wait for IO Accept socket Wait for IO Accept socket Handle request Send response etc Thread 1 Wait for IO Accept socket Thread gets descheduled from CPU but still waiting for IO Handle request Send response

Slide 47

Slide 47 text

5.times do t = Time.now # create 100k fibers ary = 100_000.times.map { Fiber.new { } } # resume and complete 100k fibers ary.each(&:resume) p Time.now - t end

Slide 48

Slide 48 text

$ jruby fiber_test.rb [7.603s][warning][os,thread] Attempt to protect stack guard pages failed (0x00007fc240a00000-0x00007fc240a04000). # # A fatal error has been detected by the Java Runtime Environment: # Native memory allocation (mprotect) failed to protect 16384 bytes for # memory to guard stack pages # # An error report file with more information is saved as: # /home/headius/work/jruby/hs_err_pid75149.log # # If you would like to submit a bug report, please visit: # https://bugreport.java.com/bugreport/crash.jsp # Aborted (core dumped) 😩

Slide 49

Slide 49 text

Project Loom • "Virtual threads" for JVM • User-mode threading • JVM handles scheduling • Thread does not have to pause • Perfect analog for Ruby's Fibers • Previews available in Java 19, 20 • Final is on track for 21!

Slide 50

Slide 50 text

No content

Slide 51

Slide 51 text

No content

Slide 52

Slide 52 text

$ jruby -J--enable-preview fiber_test.rb 2.324123 0.880373 0.6916289999999999 0.73703 0.655856 🤩

Slide 53

Slide 53 text

JRuby Today • Production deployments since 2007 • Thousands of apps around the world • Web, desktop, mobile, POS, fi nance, medicine, astronomy, government, ... aircraft refueling? 😬 • Solid performance, industry-leading in many areas

Slide 54

Slide 54 text

JRuby on Rails • It's just like Ruby on Rails • Generate app, populate database, deploy! • But with some amazing extras! • Concurrent threading for many users (CRuby uses processes) • Deploy on any Java server as a web archive • Everything else JVM has to offer!

Slide 55

Slide 55 text

JRuby Install • Install a JDK • Java 11+ recommended, there's many distributions out there • Java 8 supported for 9.4 and lower • Install JRuby • Recommended: system package, Ruby installer, Docker image • Download tarball/zip or Windows installer

Slide 56

Slide 56 text

[] jruby $ gem install rails Fetching loofah-2.21.3.gem Fetching rails-html-sanitizer-1.6.0.gem Fetching rack-2.2.8.gem Fetching activesupport-7.0.7.gem Fetching nokogiri-1.15.4-java.gem Fetching actionview-7.0.7.gem Fetching actionpack-7.0.7.gem Fetching railties-7.0.7.gem Fetching mini_mime-1.1.5.gem Fetching activemodel-7.0.7.gem Fetching activerecord-7.0.7.gem Fetching activejob-7.0.7.gem Fetching activestorage-7.0.7.gem Fetching actiontext-7.0.7.gem Fetching actionmailer-7.0.7.gem Fetching actionmailbox-7.0.7.gem Fetching actioncable-7.0.7.gem Fetching rails-7.0.7.gem ... [] jruby $ rails new carolina_app create create README.md create Rakefile create .ruby-version create config.ru create .gitignore create .gitattributes create Gemfile run git init from "." ...

Slide 57

Slide 57 text

[] carolina_app $ bin/rails generate scaffold post title:string body:text published:boolean invoke active_record create db/migrate/20230819122544_create_posts.rb create app/models/post.rb invoke test_unit create test/models/post_test.rb create test/fixtures/posts.yml invoke resource_route route resources :posts invoke scaffold_controller create app/controllers/posts_controller.rb invoke erb create app/views/posts create app/views/posts/index.html.erb create app/views/posts/edit.html.erb create app/views/posts/show.html.erb create app/views/posts/new.html.erb ...

Slide 58

Slide 58 text

[] carolina_app $ rake db:migrate == 20230819122544 CreatePosts: migrating ============ -- create_table(:posts) -> 0.0088s -> 0 rows == 20230819122544 CreatePosts: migrated (0.0097s) === [] carolina_app $ rails server => Booting Puma => Rails 7.0.7 application starting in development => Run `bin/rails server --help` for more startup options Puma starting in single mode... * Puma version: 5.6.7 (jruby 9.4.4.0-SNAPSHOT - ruby 3.1.4) ("Birdie's Version") * Min threads: 5 * Max threads: 5 * Environment: development * PID: 22710 * Listening on http://[::1]:3000 * Listening on http://127.0.0.1:3000 Use Ctrl-C to stop

Slide 59

Slide 59 text

No content

Slide 60

Slide 60 text

No content

Slide 61

Slide 61 text

Ruboto: JRuby on Android • Actively used for commercial projects today • Build interface with GUI builder, wire it up with Ruby code • Neglected a bit but being updated for JRuby 9.4 now! • ruboto.org

Slide 62

Slide 62 text

No content

Slide 63

Slide 63 text

Trying Ruboto IRB • Original was pulled from store because it asked for ALL privileges! • Duh, it was a tech demo and REPL • We will republish soon, but search for two packages to download: • Ruboto Core: JRuby and Ruboto framework as a shared package • Ruboto IRB: REPL, editor, example scripts

Slide 64

Slide 64 text

The Future of JRuby • Compatibility, performance are pretty solid right now • Best support for Ruby language, core classes of any impl • Challenges remain • ...but they keep this project fun! ☺

Slide 65

Slide 65 text

Startup and Warmup • Number one complaint from users • CRuby is basically designed to start up fast • Preloaders: Nailgun, Drip, Theine work "ok" • Native AOT: GraalVM breaks all dynamic features ☹ • Project CRaC, Project Leyden offer hope for JRuby

Slide 66

Slide 66 text

Memory Usage • We use every trick in the book to pack memory • With real threads, we can beat CRuby at 5 concurrent users • JVM is just BIG • Simple objects are 16 bytes minimum • Project Lilliput aims to halve that • New GCs pause less, use less memory, and free unused memory

Slide 67

Slide 67 text

#1 Challenge...

Slide 68

Slide 68 text

Maintenance • Ruby is a big world and JRuby is a big project • Only two of us full-time currently • Maybe 6-12 regular contributors • JRuby supports Ruby 3.1 but 3.2 is out and 3.3 in December • Optimization on hold trying to keep up!

Slide 69

Slide 69 text

HELP WANTED • Seeking new developers to help improve JRuby • Applicants must have knowledge of: • Parsers, compilers, bytecode generation, regular expressions, string encoding, cryptography, virtual threading, native interop, memory pro fi ling, code optimization, Ruby and Java and JVM • Or we'll just teach you what you need to know 😀 • We need you!

Slide 70

Slide 70 text

Wrapping Up • My journey to JRuby has been fun and exciting • We've created dozens of innovations for JVM and Ruby • I knew how to do none of this when I started • With persistence and curiousity, nothing is impossible • Why not give JRuby a try and help us move it forward?

Slide 71

Slide 71 text

Thank you! • Charles Oliver Nutter • @headius(@mastodon.social) • [email protected]