Talks

JRuby: Looking Forward

RubyKaigi 2023

00:00:02.959 Okay, let's get started. I've got a lot to cover today. I'm going to talk about JRuby and some of the cool stuff we have coming up in the future.
00:00:11.340 I'm Charles Nutter, but you can call me Charlie. That's fine with me. Here's my contact information. I've been doing JRuby development for a long time now, over 15 years, and I'm getting close to 20 years.
00:00:18.060 I've been doing full-time JRuby development since about 2006, first with Sun Microsystems, then Engine Yard, and of course, for the last 10 years, we've been thankful that Red Hat has continued to sponsor the work we're doing to make JRuby better.
00:00:36.300 So, what about JRuby? How many people here have never heard of JRuby? Okay, there's a few folks. Good! There'll be a lot of content here for everybody, whether you're new to JRuby or already familiar with it. JRuby is Ruby on top of the Java Virtual Machine (JVM). We always aim to be a Ruby implementation first, so we focus on user experience, command line functionality, and compatibility with Ruby, while integrating with the JVM as a secondary feature.
00:00:54.780 We expect that any pure Ruby code out there should just work on JRuby. If anything doesn't work properly, let us know. We have a very high level of compatibility. However, there are different extension APIs; we don't support certain C extensions that regular Ruby does. We also don't support forking, because the JVM doesn't allow it. Fortunately, we do have parallel threads, so that's something to consider when building an application.
00:01:19.500 JRuby has been in use for a long time, over 15 years, with production users from thousands of companies all over the world. We are very excited about our success in that regard. I want to take a step back and ask what is really important for a Ruby implementation. Of course, everybody always talks about performance, garbage collection, or straight-line execution, but there are many other important aspects.
00:01:40.320 Usability aspects are crucial: compatibility, startup time, user experience—how does it feel to use? Runtime features matter as well, such as garbage collection and Just-In-Time (JIT) compilation. Tooling on the platform and the platform features themselves, like building and deploying server applications or desktop applications, all play a role.
00:02:01.140 Performance is indeed important, and it has many different dimensions, including straight-line performance and scaling across various resources. All of these factors play into applications in different ways. Getting started with JRuby is quite easy. You can visit our website at jruby.org for more information.
00:02:20.280 Generally, there are only two steps: install a Java Development Kit (JDK) and then use whatever Ruby installer you're accustomed to—be it Ruby Install or RVM. That’s the quickest way to get up and running with JRuby. You can also download the tarball we ship, unpack it, and have JRuby running on your system.
00:02:45.180 Let's take a look at JRuby with Interactive Ruby (IRB). We can call into some Java libraries here. For example, we can use the Java Lang Runtime class to get an instance of the runtime, ask how many processors are available, how much memory is free on the system, and so on. Now we’re up and running in JRuby.
00:03:10.920 The most important aspect for us is compatibility. Currently, we have JRuby 9.4, which is compatible with Ruby 3.1. We released it about four months ago, around the time of RubyConf in the U.S. Compatibility levels are generally very good; we are still addressing a few edge cases, but most users are happy with its performance.
00:03:26.820 JRuby 9.3 will be maintained for a while with Ruby 2.6 compatibility, but I assume most people have moved beyond that by now. For instance, Rails 7 requires Ruby 2.7 or higher, so we're somewhat constrained by that decision. Later this year, we’ll be working on JRuby 9.5, which will include many new optimizations and Ruby 3.2 compatibility.
00:03:51.840 Here is a chart created by Benoit from the Truffle Ruby project, showing the compatibility levels based on Ruby specs—how many Ruby specs we can pass. We're happy to report that JRuby ranks very high for compatibility with command-line features and language features.
00:04:17.580 Currently, we are about 98% compatible. Some features, like flip-flops, remain unimplemented, as I want to see if anyone files a bug report about it—so far, no one has had an issue with that. We do have high compatibility with the core libraries as well. The standard library shows slightly lower compatibility due to several C extensions we don't support, but for the non-C extension libraries, we cover those excellently.
00:04:47.520 Another aspect of usability that we often have concerns about is startup time, which is probably the number one complaint from people using JRuby. We are fighting an uphill battle here as the JVM just isn’t designed for quick startup. Most core JDK classes start out interpreted in the JVM, and JRuby makes this a little worse by parsing all the Ruby code with our interpreter, which in turn is interpreted on the JVM.
00:05:10.110 Our Ruby compiler is also interpreted by the JVM. Eventually, things get up and running as we turn it into bytecode, which the JVM JIT compiles, but this process takes time. Here’s a more graphical representation of the current architecture of JRuby.
00:05:31.680 JRuby pulls in Ruby code on the left side, which goes into our parser and compiler, getting transformed into Ruby intermediate representation, which we interpret for a while. Once the code heats up sufficiently, we compile it into JVM bytecode, utilizing features like invoke-dynamic to optimize performance. The JVM then takes over and runs through its optimization cycle.
00:05:56.520 The result is a significantly slower startup for something simple, like a 'hello world' command, taking over one and a half seconds compared to what C Ruby would do in about 53 milliseconds. This obviously isn't ideal. One of the first ways we've tackled this issue is by simply doing less of these optimizations, getting to the final stage more quickly.
00:06:22.920 This is where we have JRuby's Dev mode. You can pass the ‘--dev’ flag to JRuby, which simplifies JIT and tunes the JVM’s JIT to be a bit less aggressive, helping speed up some command-line operations.
00:06:38.520 Here’s a test with the 'hello world' command using the ‘-E1’ flag. This results in a 25% improvement in startup time, and it has an even greater impact on longer-running commands. For example, JRuby generating a Rails application shows a significant improvement—over 50% faster with the ‘--dev’ flag.
00:07:03.180 We are also exploring the possibility of ahead-of-time (AOT) compilation for the JVM or Java platform. The GraalVM native image compiler is one of the well-known methods for achieving faster startup, as it’s what Truffle Ruby uses.
00:07:28.740 Additionally, we’re looking forward to a project called Leiden from the OpenJDK team, which will provide a more standardized method of AOT compilation. This will allow JRuby to maintain new code running and re-optimize it while improving startup time for small commands.
00:07:52.920 Here's an example from Truffle Ruby in their JVM mode, which registers a startup time of 2.3 seconds using their native executable and some additional optimizations, making it even faster than C Ruby's startup. However, we still have to load and interpret Ruby code, and optimize it, which contributes to longer startup times.
00:08:17.880 When considering longer-running commands, our ‘--dev’ flag is actually better at cutting down startup time compared to native compilation. Thus, we have to find the right balance when speeding up startup time.
00:08:42.180 Additionally, the last area we are exploring for improving startup time is 'snapshotting.' There is a feature in Linux called checkpoint and restore in user space, which is now starting to integrate into JVMs such as IBM’s Semaru version. We have also built a working prototype for JRuby that takes advantage of checkpointing and restoring, which speeds up JRuby.
00:09:05.760 The idea is to start up the JVM, perform some operations to warm up JRuby so that it compiles itself, and saves a snapshot. From this point onward, every time we start JRuby again, we will begin from this checkpoint with an already warm virtual machine, resulting in faster startups.
00:09:29.040 For a quick test with JRuby using checkpointing for a simple 'hello world' command shows significant improvement, bringing the startup time competitive with that of C Ruby. The same performance can be observed when generating larger commands like creating a new Rails application, effectively reducing startup time under a second.
00:09:54.960 Now let's discuss some of the runtime features that make JRuby particularly interesting. We have access to all the JVM garbage collectors (GCs), and new ones are continually emerging. These GCs can be tuned in various ways depending on your needs.
00:10:23.160 If you want fast allocations, you can use the parallel garbage collector. If you're handling a large amount of data, you might consider G1 or ZGC, which are designed to manage old data efficiently in memory while minimizing pause times during garbage collection.
00:10:49.020 Many of these GCs are built into OpenJDK and can be easily used with JRuby by simply passing a flag. One great tool available for monitoring is Visual VM, which shows a live view of the garbage collector's activity.
00:11:14.340 You can view CPU usage, the number of loaded classes, threads, and monitor garbage collector generations to see how memory management is functioning in real-time.
00:11:40.260 The JVM also has a world-class Just-In-Time (JIT) compiler called HotSpot, which is widely deployed. This JIT provides various optimization tiers for quick performance improvements initially, before applying more aggressive optimizations down the line.
00:12:00.720 Currently, we’re experimenting with the Graal JIT, which can be plugged into the JVM for more aggressive optimizations—especially beneficial for numeric algorithms—leading to substantial speed improvements for JRuby.
00:12:28.920 In addition, there are other JVMs like OpenJ9 (IBM Semaru) that have their own JIT optimizations, allowing users to save compiled classes to disk, improving execution speed for subsequent runs.
00:12:54.900 Moreover, monitoring and profiling are essential aspects. Apart from Visual VM, the Flight Recorder feature of the JVM, driven through a GUI application called Mission Control, provides a wealth of insights.
00:13:18.840 In Mission Control, you can select the JRuby application to start a recording. Once the recording finishes, you will see allocations, helping you identify excessive object allocations, trace them back to specific lines in Ruby code, and monitor thread activity.
00:13:41.280 One of the exciting features coming to the JVM world is Project Loom. Historically, JRuby has implemented the Fiber class, but without the ability to control the call stack on the JVM, we had to simulate fibers using native threads.
00:14:04.080 This approach faces challenges, especially with trying to spin up thousands of fibers, as most systems cannot allow that much resource consumption. Project Loom brings native fibers to the JVM, allowing us to offer lightweight threading and fiber support in JRuby.
00:14:27.840 A benchmark demonstrates the problem with the existing fiber approach. For example, if I create 100,000 fibers that immediately yield, running them along JRuby results in an overload of threads, potentially crashing the application.
00:14:51.720 With Loom's support integrated currently into JRuby, this shifts things significantly. We appreciate how easily these fibers can start, resume, and complete tasks in a very short amount of time. This has opened up new possibilities for us.
00:15:14.760 Historically at RubyKaigi, Samuel Aitkins presented his vision for handling a tremendous number of sockets simultaneously—100k or 1 million K within a single Ruby process. With the update on Loom, we can achieve this now.
00:15:35.520 Also, I look forward to implementing async IO and the complete stack in JRuby to support Falcon servers, which could potentially be a significant area for contributions.
00:15:56.520 The final runtime feature I want to mention is Project Panama, which is progressing well and will allow us to call native C libraries directly from the JVM.
00:16:15.840 Currently, JRuby utilizes a foreign function interface (FFI), permitting access to POSIX libraries. With Panama, JVM will optimize calls to these libraries, enhancing performance. This will significantly change how we interact with native libraries.
00:16:38.640 Panama also provides a foreign memory API, allowing for more efficient usage of off-heap memory. What's most exciting is a tool called JExtract that automatically generates binding codes from header files.
00:17:02.280 This means by pointing JExtract at a header file, it can generate the JNI bindings required, making native library calls simpler and faster than before, thus improving our development experience.
00:17:27.840 We are excited to implement this approach with our SQLite JDBC adapter, which, through previous methods, faced performance penalties operating via the Java Native Interface (JNI). A proof of concept using Panama indicates a promising double performance improvement.
00:17:52.680 We are also working on integrating YARP—Yet Another Ruby Parser—into JRuby. This project has been discussed, and following this talk, Kevin will delve deeper into YARP. As there are many AST nodes that we have already integrated into JRuby, early tests indicate about a 20% improvement in startup speed compared to the current JRuby parser.
00:18:16.860 On that end, we are still utilizing JNI, and once we bring Panama into play for YARP, it should streamline our efforts even further for straightforward native library access.
00:18:41.940 Now, let’s consider GUIs. A real advantage of the JVM is the support for numerous cross-platform GUI libraries. The most well-known is Swing, a set of Java widgets for desktop applications that is consistently available across platforms.
00:19:07.260 There’s also the Scalable Windowing Toolkit (SWT), part of the Eclipse project. This allows for native widgets when creating desktop applications, making them look more like standard applications across different OS platforms, including macOS.
00:19:33.960 Then, there is JavaFX, a scene builder GUI library used for building modern applications. It requires a few more libraries to pull down, but it’s worthwhile.
00:20:01.560 We also have a JRubyFX project that allows you to wrap JavaFX with Ruby code, making application development easier for Rubyists. With other wrappers like Shoes and Glimmer, building cross-platform applications has never been simpler.
00:20:30.480 Aside from desktop applications, remember that most of us carry graphical computers in our pockets through our Android phones. We are updating Rubato, JRuby's support for Android, providing tooling and wrappers for Android APIs.
00:20:55.200 Rubato allows you to build applications wholly in Ruby. A classic example showcases Rubato IRB, an interactive Ruby shell running on mobile.
00:21:19.920 We previously couldn't publish our application due to excessive permissions but are working within new security requirements to republish it soon. Until then, users can manually install the APK.
00:21:46.560 Currently, we're upgrading Rubato IRB to JRuby 9.4, ensuring Ruby 3.1 compatibility, and we're making significant progress throughout the application.
00:22:07.200 To wrap up, I'll discuss performance and scaling. Deploying applications in CRuby has historically faced the issue of a lack of concurrent threads and parallelism at the operating system level. This necessitates worker processes, resulting in resource inefficiencies.
00:22:35.880 You can utilize techniques like copy-on-write and pre-loading servers to mitigate some overhead. However, JRuby presents a better solution compared to CRuby, allowing multiple users to run on a single server process without wasting memory.
00:23:01.560 In a benchmark involving a basic Rails application, we generated a blog post and tested concurrent requests using both JRuby and CRuby with different threading methods.
00:23:19.320 Here with JRuby, I was able to run 16 threads for 16-way concurrency, compared to set worker limits in CRuby. The results indicated that JRuby generally achieved better performance.
00:23:39.660 Even with a warm-up time evident for JRuby, several requests started generating results at higher requests per second once stable. The warm-up curve is more extended, but it surpasses performance expectations.
00:24:02.040 Yet memory usage remains a critical aspect. By default, the JVM utilizes memory aggressively, resulting in JRuby using around 3.5 gigabytes. However, we can configure it to optimize memory, which reduces our memory footprint.
00:24:31.680 By limiting the JVM's heap size to approximately 300 megabytes, we found JRuby drops below one gigabyte of memory usage, relative performance doesn’t degrade much during this tuning.
00:24:55.560 For CRuby setups, the 1.6 GB without YJIT or around 2 GB with YJIT reflects considerable memory consumption without optimal scaling. Nonetheless, we can adjust our instances and optimize costs effectively.
00:25:20.760 The key takeaway is to maximize usage efficiency and lower costs while achieving better overall performance. This reflects significant savings in cloud service costs as well.
00:25:41.400 In conclusion, JRuby 9.4 continues to stabilize, and we are excited about the global production adoption. Please give it a try and provide feedback. As we catch up on compatibility, our focus will also shift back to optimizations after several years.
00:26:01.320 With JRuby 9.5 expected later this year, we'll integrate Ruby 3.2 features while aiming for another round of optimizations. I hope this has illustrated how many aspects of JRuby and the JVM we're starting to leverage.
00:26:32.220 With projects like Loom and Panama, combined with mobile development support and plenty of opportunities for desktop app development, JRuby holds promise for diverse applications. Whatever interests you, we believe JRuby can be immensely helpful.
00:27:01.800 Thank you for your attention, and I look forward to seeing you around the conference. Also, I have a limited supply of JRuby stickers for anyone interested, so come and see me afterward.
00:27:43.440 Thank you!