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

Optimizing JRuby 10

headius
April 20, 2025

Optimizing JRuby 10

JRuby 10 is out now with Ruby 3.4 and Rails 8 compatibility! After years of catching up on features, we've finally been able to spend time on long-delayed optimizations. This talk will show some of the best examples, including real-world application performance, and teach you how to find and fix performance problems in your JRuby applications.

Presented on April 18, 2025 at RubyKaigi 2025 in Matsuyama, Japan.

headius

April 20, 2025
Tweet

More Decks by headius

Other Decks in Programming

Transcript

  1. ͜Μʹͪ͸! • Charles Oliver Nutter • @headius • @mastodon.social •

    .bsky.social • [email protected] • JRuby developer for 20 years • Unemployed?
  2. 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
  3. 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
  4. 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
  5. 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
  6. 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
  7. 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
  8. 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
  9. 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
  10. 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!
  11. 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
  12. 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
  13. 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)
  14. 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
  15. 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)
  16. 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
  17. 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)
  18. 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
  19. Create, resume, and fi nish 1000 fi bers 0 75

    150 225 300 Java 8 Java 21 288 iters/s 49 iters/s
  20. 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
  21. 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
  22. 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 🤯
  23. 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
  24. 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
  25. Data • New immutable struct type in Ruby 3.2 •

    Compact storage • Special #new method to allocate and initialize together
  26. 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
  27. 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
  28. 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
  29. 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
  30. 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
  31. 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
  32. 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
  33. 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
  34. 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!
  35. 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!