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

JRuby: Looking Forward

JRuby: Looking Forward

Over the past few years, we've update JRuby to support Ruby 3.1 compatibility and new JVM features like native fibers and FFI. Now we're returning to performance work, catching up with the latest optimizations. In this talk we'll explore some popular benchmarks and show how we're improving JRuby across the board. Well also discuss the future of JRuby and how Rubyists can help.

Delivered May 12, 2023 at RubyKaigi in Matsumoto, Japan.

headius

May 12, 2023
Tweet

More Decks by headius

Other Decks in Programming

Transcript

  1. Looking Forward!

    View full-size slide

  2. Who Am I
    • Charles Oliver Nutter


    • @headius(@mastodon.social)


    [email protected]


    • JRuby developer since 2004


    • Full-time JRuby and JVM language
    advocate since 2006

    View full-size slide

  3. What is JRuby?
    • Ruby atop the Java Virtual Machine


    • Ruby implementation
    fi
    rst, JVM language second


    • Many bene
    fi
    ts from JVM ecosystem


    • Ruby code should "just work"


    • Different extension API, no forking, parallel threads


    • Thousands of production users, 15+ years of real-world use

    View full-size slide

  4. What Is Important?
    • Usability: compatibility, startup time, warmup time


    • Runtime features: GC, JIT, monitoring, pro
    fi
    ling, concurrency


    • Platform features: mobile, server, desktop, integration, deployment


    • Performance: straight line, scaling, parallelism, resource usage


    • Different applications needs different capabilities

    View full-size slide

  5. Getting Started

    View full-size slide

  6. 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: Ruby installer, system package, Docker image


    • Download tarball/zip or Windows installer

    View full-size slide

  7. Test it out!
    [] ~ $ rvm use jruby


    Using /Users/headius/.rvm/gems/jruby-9.4.2.0


    [] ~ $ irb


    >> runtime = java.lang.Runtime.runtime


    => #


    >> runtime.available_processors


    => 8


    >> runtime.free_memory


    => 91420584


    >>

    View full-size slide

  8. Ruby Compatibility
    • JRuby 9.4 is Ruby 3.1 compatible
    • 98% of language specs passing


    • Nearly complete core + stdlib features from 2.7, 3.0 and 3.1


    • JRuby 9.3 (Ruby 2.6 compat) maintaned through 2023


    • JRuby 9.5 with 3.2 support before 2024 (help us!)


    • Compatibility before performance!

    View full-size slide

  9. Very high language compatibility
    High core compatibility,
    missing parts mostly POSIX things
    Most of stdlib works well,
    MRI-speci
    fi
    c extensions excluded
    Nearly all command line works same
    https://eregon.me/rubyspec-stats/

    View full-size slide

  10. Startup Time
    • JVM is not designed to start up quickly


    • Most of core JDK starts in interpreter


    • Long tail to optimize code and reach peak performance


    • JRuby makes it worse


    • Ruby parser, compiler and interpreter are interpreted by JVM


    • Lazy compile to bytecode, bytecode is interpreted by JVM

    View full-size slide

  11. JRuby Architecture
    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

    View full-size slide

  12. ruby -e 1
    0s
    0.45s
    0.9s
    1.35s
    1.8s
    -e 1
    1.686s
    0.053s
    CRuby 3.2 JRuby 9.4

    View full-size slide

  13. JRuby With Full Optimization
    Ruby (.rb)
    JIT
    Java Instructions


    (java bytecode)
    Ruby Instructions


    (IR)
    parse
    interpret
    interpreter
    interpret
    better


    native code
    java


    bytecode


    interpreter
    C1 compile
    native code


    execute
    C2 compile
    Java Virtual Machine
    JRuby Internals

    View full-size slide

  14. JRuby --dev Mode
    Ruby (.rb) Ruby Instructions


    (IR)
    parse
    interpret
    interpreter
    Java Virtual Machine
    JRuby Internals
    C1 compile
    interpreter as
    native code
    execute

    View full-size slide

  15. ruby -e 1
    0s
    0.45s
    0.9s
    1.35s
    1.8s
    -e 1
    1.271s
    1.686s
    0.053s
    CRuby 3.2 JRuby 9.4 JRuby 9.4 --dev

    View full-size slide

  16. rails new testapp --skip-bundle
    0s
    1.5s
    3s
    4.5s
    6s
    rails new testapp --skip-bundle
    2.7s
    5.918s
    0.314s
    CRuby JRuby JRuby --dev

    View full-size slide

  17. Ahead-of-time Compilation
    • Maybe we can start with native code?


    • GraalVM Native Image is a well known option


    • Disables most dynamic JVM features


    • Project Leyden: standardized AOT for Hotspot JVM


    • Start native, but support and optimize dynamic stuff later!

    View full-size slide

  18. ruby -e 1
    0s
    0.75s
    1.5s
    2.25s
    3s
    -e 1
    0.034s
    2.308s
    1.271s
    1.686s
    0.053s
    CRuby 3.2 JRuby 9.4 JRuby 9.4 --dev Tru
    ffl
    eRuby --jvm Tru
    ffl
    eRuby native

    View full-size slide

  19. rails new testapp --skip-bundle
    0s
    4s
    8s
    12s
    16s
    rails new testapp --skip-bundle
    3.218s
    15.676s
    2.7s
    5.918s
    0.314s
    CRuby JRuby JRuby --dev Tru
    ffl
    eRuby --jvm Tru
    ffl
    eRuby native

    View full-size slide

  20. Snapshot a "Warm" JRuby?
    • Pre-optimize and save a snapshot?


    • CRIU: Checkpoint and Restore in Userspace (Linux only)


    • IBM Semeru JDK "InstantOn"


    • Working prototype with JRuby!


    • CRaC: OpenJDK support for checkpoint and restore


    • Cross-platform and standardized (coming soon?)

    View full-size slide

  21. ruby -e 1
    0s
    0.45s
    0.9s
    1.35s
    1.8s
    -e 1
    0.21s
    1.271s
    1.686s
    0.053s
    CRuby 3.2 JRuby 9.4 JRuby 9.4 --dev JRuby 9.4 CRIU

    View full-size slide

  22. rails new testapp --skip-bundle
    0s
    1.5s
    3s
    4.5s
    6s
    rails new testapp --skip-bundle
    0.89s
    2.7s
    5.918s
    0.314s
    CRuby JRuby JRuby --dev JRuby CRIU

    View full-size slide

  23. Runtime Features

    View full-size slide

  24. JVM GC
    • Many options to tune JVM GCs


    • Heap size: small or large?


    • Throughput: faster allocations or shorter pause times?


    • Working set: large in-memory or mostly new objects?


    • Many options in standard OpenJDK


    • Serial, Parallel, G1, ZGC, Shenandoah, MMtk

    View full-size slide

  25. JVM JIT
    • HotSpot JIT is most widely deployed


    • C1 "client" JIT: fast, simple optimizations


    • C2 "server" JIT: needs pro
    fi
    le data, heavy optimization


    • Both enabled with various "tiers" of JIT


    • Graal JIT: newer, more aggressive optimizations


    • OpenJ9 JIT (IBM JDK): of
    fl
    ine AOT, JIT servers, CRIU

    View full-size slide

  26. Monitoring and Pro
    fi
    ling
    • VisualVM: GUI console for basic JVM monitoring


    • JDK Flight Recorder: always-on monitoring with pro
    fi
    ling options


    • Low-overhead 1% to 5%, built into OpenJDK


    • JDK Mission Control: GUI client for Flight Recorder data


    • https://adoptium.net/jmc/

    View full-size slide

  27. Project Loom
    • JRuby's
    fi
    bers are based on threads


    • Too many, JVM blows up!


    • Scheduling, resource-intensive


    • Loom brings
    fi
    bers to JVM


    • Easily handles thousands of
    fi
    bers


    • Faster context-switching

    View full-size slide

  28. 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

    View full-size slide

  29. $ 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/jruby93/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)
    😩

    View full-size slide

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

    View full-size slide

  31. C100k on JRuby?
    • See @ioquatix talks on async and falcon!


    • Just starting to add support for io-event, async-io in JRuby


    • To do: Fiber/IO scheduler and Buffer APIs


    • Great way to contribute!

    View full-size slide

  32. Project Panama: FFI for JVM
    • Foreign function interface (FFI)


    • With JVM help to make direct calls


    • Foreign memory API


    • JVM-assisted access, lifecycle


    • API extraction from C/++ headers


    • Save time setting up bindings

    View full-size slide

  33. jextract code generator
    • Writing bindings using FFI or Fiddle
    is still challenging


    • Parameter sizes, struct layout, in
    and out values, pointers


    • Differences across platforms


    • jextract: produce Panama/FFI stub
    code from a C header
    fi
    le


    • Call that stub code from JRuby...
    easy FFI for all!
    //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;
    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);

    View full-size slide

  34. SQLite JDBC Adapter
    • Java DataBase Connectivity (JDBC) wrapper around SQLite


    • Used by JRuby for ActiveRecord, Sequel


    • Java Native Interface (JNI) currently, limits throughput


    • Proof-of concept Panama-based version working now


    • 2x performance for most operations

    View full-size slide

  35. Yet Another Ruby Parser (YARP)
    • Simple C library for parsing Ruby
    that we can share


    • JRuby already integrating it!


    • 60% of nodes implemented,

    `gem list` almost runs!


    • 20% faster startup for "-e 1"


    • Using JNI, but could be even
    faster with Panama

    View full-size slide

  36. GUIs, Graphics, and Games

    View full-size slide

  37. GUI Libraries
    • Swing, built into JDK


    • Clean, cross-platform, easy to build simple UIs


    • Scalable Windowing Toolkit (Eclipse SWT)


    • Native widgets, WebKit browser component, rich ecosystem


    • JavaFX (via JRubyFX, github/jruby/jrubyfx)


    • Scene-based, vector drawing, event-driven modern UI library

    View full-size slide

  38. Glimmer
    • Glimmer GUI DSL


    • Multiple backends (SWT, GTK, ...)


    • JRuby + SWT is the most mature


    • JRuby makes cross-platform GUI
    much easier!


    • Works same everywhere


    • GUI libraries shipped with gem

    View full-size slide

  39. JRubyFX
    • JRuby wrapper for JavaFX


    • Supports FXML layout or direct
    widget scripting


    • Could be combined with Ruboto for
    FX on Android apps!

    View full-size slide

  40. GUIs... like Android?

    View full-size slide

  41. Ruboto: JRuby on Android
    • ruboto.org,

    https://github.com/ruboto/ruboto


    • 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!

    View full-size slide

  42. Ruboto IRB
    • IRB in JRuby on Android!


    • Plus an editor and script runner


    • Not currently in the store, but we will
    republish soon!


    • Search for "Ruboto Core" and
    "Ruboto IRB" APKs

    View full-size slide

  43. Updated Ruboto!
    • We are updating Ruboto!


    • JRuby 9.4, with Ruby 3.1 support


    • Latest versions of Android


    • Still a few bugs to work through


    • https://github.com/ruboto/JRuby9k_POC


    • jruby_9_4 branch


    • bundle install ; rake assemble

    View full-size slide

  44. Performance and Scaling

    View full-size slide

  45. Scaling Applications
    • Classic problem on CRuby/MRI


    • No concurrent threads, so we need worker processes


    • Processes duplicate runtime state and waste resources


    • JRuby is a good solution


    • Multi-threaded single process runs your entire site
    • Single process with leading-edge GC uses resources better

    View full-size slide

  46. Baseline Rails App
    • Scaffolded "blog" application on
    PostgreSQL, Puma


    • IBM VPC instance: 8 vCPU, 32GB


    • CRuby 3.2, 16 workers


    • JRuby 9.4: 16 threads


    • Database, siege benchmark driver
    on same instance

    View full-size slide

  47. Requests per second
    0
    450
    900
    1350
    1800
    60s siege iteration
    1 2 3 4 5 6 7
    JRuby CRuby 3.2 CRuby 3.2 + YJIT

    View full-size slide

  48. requests per second (higher is better)
    0rps
    450rps
    900rps
    1350rps
    1800rps
    1,550rps
    1,280rps
    1,705rps
    JRuby 9.4 CRuby 3.2 CRuby + YJIT

    View full-size slide

  49. Memory
    • JRuby: 3.4GB RSS


    • JRuby with 300MB heap: 955MB RSS


    • JRuby G1: 1.6G


    • CRuby: 16x 103MB = 1.6GB


    • CRuby YJIT: 16x 125MB = 2GB

    View full-size slide

  50. requests per second (higher is better)
    0rps
    450rps
    900rps
    1350rps
    1800rps
    1,643rps
    1,550rps
    1,280rps
    1,705rps
    JRuby 9.4 CRuby 3.2 CRuby + YJIT JRuby 300MB heap

    View full-size slide

  51. RPS 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.775 rps/MB
    0.8 rps/MB
    0.501 rps/MB
    JRuby 9.4 CRuby 3.2 CRuby + YJIT JRuby 300MB heap

    View full-size slide

  52. RPS per MB of memory (160-way concurrency)
    0rps
    3.5rps
    7rps
    10.5rps
    14rps
    13.692 rps/MB
    0.775 rps/MB
    0.8 rps/MB
    4.871 rps/MB
    JRuby 9.4 CRuby 3.2 CRuby + YJIT JRuby 300MB heap
    ¥¥¥¥

    View full-size slide

  53. JRuby Future
    • JRuby 9.4 continues to stabilize


    • Big optimization work coming the rest of this year


    • JRuby 9.5: Java 17, Ruby 3.2, many optimizations


    • Lots of new JVM features to leverage


    • Very exciting time for Rubyists!


    • Try JRuby today and let us know how it goes!

    View full-size slide

  54. Thank You!
    • Charles Oliver Nutter


    [email protected]


    • @headius(@mastodon.social)


    • https://github.com/jruby/jruby


    • https://www.jruby.org

    View full-size slide