Slide 1

Slide 1 text

Optimizing JRuby 10 Charles Oliver Nutter

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

͜Μʹͪ͸! • Charles Oliver Nutter • @headius • @mastodon.social • .bsky.social • [email protected] • JRuby developer for 20 years • Unemployed?

Slide 4

Slide 4 text

Supporting JRuby • Actually, I work for you! • I love helping Ruby users with JRuby • I want to keep making JRuby better • Sponsorships from users • Every little bit helps! • Headius Enterprises JRuby support • Migration, updates, performance work

Slide 5

Slide 5 text

JRuby 10 Released! • Ruby 3.4 compatible • Thousands of new specs, tests, assertions passing • Minimum Java 21 to leverage modern JVM features • On track for greater performance work this year

Slide 6

Slide 6 text

Optimizing What? • Developer happiness like Ruby • Opportunities for Ruby to grow and evolve • Compatibility with new Ruby, libraries • Startup and warmup time • Straight-line performance • Multi-core performance

Slide 7

Slide 7 text

Optimizing Compatibility

Slide 8

Slide 8 text

Optimizing Compatibility • Tracking each Ruby release is dif fi cult for a small team • JRuby 9.1: Ruby 2.3 compatible • JRuby 9.2: Ruby 2.5 compatible • JRuby 9.3: Ruby 2.6 compatible • JRuby 9.4: Ruby 3.1 compatible • JRuby 10 makes a big leap to Ruby 3.4

Slide 9

Slide 9 text

Chasing CRuby 1.8 1.9 2.0 2.1 2.2 2.3 2.4 2.5 2.6 2.7 2.8 2.9 3.0 3.1 3.2 3.3 3.4 3.5 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 CRuby JRuby

Slide 10

Slide 10 text

Compatibility Lag 0 275 550 825 1100 Days behind CRuby release 2.2 2.3 2.5 2.6 3.1 3.4 111 333 1,002 150 130 209

Slide 11

Slide 11 text

Commit Count > 20 since Apr '24 782 Nobuyoshi Nakada 695 Hiroshi SHIBATA 541 David Rodríguez 486 Kevin Newton 434 Peter Zhu 248 Jean Boussier 203 git[bot] 137 Yusuke Endoh 133 BurdetteLamar 127 Alan Wu 127 ydah 110 dependabot[bot] 107 tomoya ishida 105 Stan Lo 96 Burdette Lamar 92 Takashi Kokubun 77 Kazuki Yamaguchi 67 Earlopain 1030 Charles Oliver Nutter 539 Thomas E. Enebo 21 Oliver Nutter 58 Matt Valentine-House 53 Jeremy Evans 47 Aaron Patterson 46 Koichi Sasada 45 Naoto Ono 45 Samuel Williams 44 David Rodriguez 43 yui-knk 33 Benoit Daloze 30 Mari Imaizumi 28 John Hawthorn 28 Misaki Shioi 23 KJ Tsanaktsidis 22 Andrew Konchin 22 Samuel Giddins 21 tompng 21 Étienne Barrié 20 S-H-GAMELINKS Help! 🙇 CRuby committers JRuby committers

Slide 12

Slide 12 text

Rails 8 Compatible? • Dif fi cult question! • Everything that is pure Ruby is expected to work • Anything using native code must be ported or adapted • Rails requires Ruby 3.2 • With JRuby 10, most of Rails 8 should run on JRuby

Slide 13

Slide 13 text

Chasing ActiveRecord • ActiveRecord has the most CRuby-speci fi c code • Native drivers each with their own API quirks • Large amounts of DB-speci fi c code for each driver • All must be ported or adapted for ActiveRecord-JDBC • This is the main reason for compatibility lag • Help wanted!

Slide 14

Slide 14 text

Optimizing Startup

Slide 15

Slide 15 text

Optimizing Startup • First impressions are important! • JRuby has always had much longer startup than CRuby • JVM must load, parse, optimize each time • Delaying parts of startup only helps "hello world" • Newer JDKs shipping improved startup features

Slide 16

Slide 16 text

JRuby Compiler Pipeline Ruby (.rb) JIT Java Instructions (java bytecode) Ruby Instructions (IR) parse interpret interpreter interpret C1 compile native code better native code java bytecode interpreter execute C2 compile Java Virtual Machine JRuby Internals

Slide 17

Slide 17 text

JVM Startup Projects • Application Class Data Store (AppCDS) • Pre-cache classes, methods, code, metadata across runs • Machine code caching (AOTCache) • AppCDS + execution pro fi les and native code • Coordinated restore at checkpoint (CRaC) • Capture entire process state and restore later (repeatedly)

Slide 18

Slide 18 text

0s 0.35s 0.7s 1.05s 1.4s -e 1 0.17 0.712 0.847s 1.051s 1.001s 1.327s 0.064s CRuby 3.4 JRuby 9.4 JRuby 9.4 --dev JRuby 10 JRuby 10 --dev JRuby 10 --dev AOTCache JRuby on CRaC

Slide 19

Slide 19 text

Optimizing Performance

Slide 20

Slide 20 text

Optimizing Performance • JRuby depends on JVM for most optimization • JVM has world-class JIT compilers, garbage collectors • Similar design to ZJIT, but with 30 years of work in it • JRuby itself has an IR, basic block-based interpreter and JIT • Similar design to ZJIT starting in JRuby 9.0 (2015)

Slide 21

Slide 21 text

Pure Ruby red/black tree 0 20 40 60 80 red/black bench iters/s 76.8 iters/sec 65.5 iters/sec 15.1 iters/sec CRuby 3.4 CRuby 3.4 YJIT JRuby 10

Slide 22

Slide 22 text

Better Extension Performance 0M 0.3M 0.6M 0.9M 1.2M small medium large 0.13 0.29 1.1 0.06 0.11 0.72 MRI (oj) JRuby (oj) Millions of loads per second (higher is better)

Slide 23

Slide 23 text

Optimizing Fibers • Fibers were not well-supported on older JVMs • Only abstraction was a native thread, too heavy for 1000s of fi bers • OpenJDK Project Loom adds fi bers to JVM • "Virtual threads" are lightweight, but use same thread API • JRuby can match CRuby for count and perf of fi bers

Slide 24

Slide 24 text

1000.times.map { Fiber.new { Fiber.yield } }.each(&:resume).each(&:resume)

Slide 25

Slide 25 text

Create, resume, and fi nish 1000 fi bers 0 75 150 225 300 Java 8 Java 21 288 iters/s 49 iters/s

Slide 26

Slide 26 text

Class#new Optimization • Aaron Patterson adding opt_new to optimize Class#new • Calling C code to allocate + initialize hurts performance • JRuby implemented this in 2016 • If Class#new is default, allocate and call #initialize inline • Allocation and initialize both inline back to caller

Slide 27

Slide 27 text

Object.new

Slide 28

Slide 28 text

0M 3.5M 7M 10.5M 14M Object.new allocations per second 0M 13M 10.3M Ruby 3.4 + YJIT Ruby 3.5 + opt_new JRuby 10

Slide 29

Slide 29 text

0M 75M 150M 225M 300M Object.new allocations per second 217.5M 13M 10.3M Ruby 3.4 + YJIT Ruby 3.5 + opt_new JRuby 10 🤯

Slide 30

Slide 30 text

1M 10M 100M 1000M 10000M Object.new allocations per second 217.5M 13M 10.3M Ruby 3.4 + YJIT Ruby 3.5 + opt_new JRuby 10

Slide 31

Slide 31 text

Allocation Pro fi le

Slide 32

Slide 32 text

class Foo def initialize = nil end

Slide 33

Slide 33 text

1M 10M 100M Object.new allocations per second Foo.new allocations per second 216M 217.5M 22.1M 13M 10.3M 10.3M Ruby 3.4 + YJIT Ruby 3.5 + opt_new JRuby 10

Slide 34

Slide 34 text

0M 750M 1500M 2250M 3000M Foo.new allocations per second 2,591.1M 216M JRuby 10 JRuby 10 + Graal 🤯

Slide 35

Slide 35 text

Data • New immutable struct type in Ruby 3.2 • Compact storage • Special #new method to allocate and initialize together

Slide 36

Slide 36 text

Bar = Data.new(:a, :b, :c) Bar.new(1, 2, 3)

Slide 37

Slide 37 text

0M 1.75M 3.5M 5.25M 7M Bar.new(1, 2, 3) allocations per second 6.2M 2.9M CRuby 3.4 + YJIT JRuby 10

Slide 38

Slide 38 text

Optimizing Concurrency

Slide 39

Slide 39 text

Threads or Ractors? • Ractors are designed to bring concurrency to Ruby • You must write Ractor-friendly code • High overhead crossing Ractor boundary • Threads in JRuby are already 100% concurrent • You must right Thread-friendly code • But zero overhead due to shared object

Slide 40

Slide 40 text

if THREADS queues = CONCURRENT.times.map { Queue.new } threads = CONCURRENT.times.map do |i| Thread.new do queue = queues[i] data_chunk = queue.pop data_chunk.each { |item| JSON.parse(item) } end end end

Slide 41

Slide 41 text

result = Benchmark.measure do if THREADS CONCURRENT.times do |i| queues[i] << data[i] end threads.each(&:join) else # Linear without any threads data.each do |piece| piece.each { JSON.parse(_1) } end end end

Slide 42

Slide 42 text

Threads vs Ractors 0 1.25 2.5 3.75 5 times faster than linear 4.2 1.2x 0.4x 1.1x CRuby threads CRuby ractors CRuby ractors + patch JRuby threads

Slide 43

Slide 43 text

Better Scaling requests per second per MB of memory (16-way concurrency) 0rps/mb 0.45rps/mb 0.9rps/mb 1.35rps/mb 1.8rps/mb 1.72 rps/MB 0.92 rps/MB 0.8 rps/MB CRuby CRuby + YJIT JRuby 300MB heap One JRuby process can run your entire site

Slide 44

Slide 44 text

Optimizing Opportunities

Slide 45

Slide 45 text

Optimizing Opportunities • JRuby runs anywhere Java runs... which is everywhere! • Rubyists can build programs for Java organizations • Ruby can use Java libraries and JVM features • JRuby applications can be a single .jar fi le • All dependencies plus JRuby plus your app plus obfuscation! • Many JRuby users sell commercial software this way

Slide 46

Slide 46 text

Java Swing from Ruby # Create frame and button frame = javax.swing.JFrame.new("Hello Swing") button = javax.swing.JButton.new("Click Me!") button.add_action_listener { button.text = "Clicked!" } # Add the button to the frame frame.get_content_pane.add(button) # Show frame frame.set_default_close_operation(JFrame::EXIT_ON_CLOSE) frame.pack frame.visible = true

Slide 47

Slide 47 text

Graphics GUIs and Games Shoes 4 Glimmer Minecraft

Slide 48

Slide 48 text

JRuby on Android ruboto.org

Slide 49

Slide 49 text

JRuby Future

Slide 50

Slide 50 text

JRuby 10.x • Many more optimizations coming! • Now that we are 3.4 compatible, I can work on fun stuff • If you fi nd something slower than CRuby, tell me! • More JDK features • AOTCache for startup, Panama for fast native calls • Upgrade JVM, your JRuby code runs faster!

Slide 51

Slide 51 text

Help Me Help You! • JRuby can solve many problems for the Ruby community • If JRuby goes away it will hurt the Ruby community • Please try JRuby and let us know how we can help you! • If you use JRuby, let's talk about a partnership!

Slide 52

Slide 52 text

Ͳ͏΋͋Γ͕ͱ͏͍͟͝·͢! • Charles Oliver Nutter • @headius • @mastodon.social • .bsky.social • [email protected] • Help me keep working on JRuby!