Charles Nutter

JRuby at 15 Years: Meeting the Challenges

RubyKaigi2017
http://rubykaigi.org/2017/presentations/headius.html

JRuby has evolved a lot over 15 years. We've met challenges of performance, native integration, and compatibility. What will we face in the future? In this talk we'll discuss today's JRuby challenges: startup time, code size, type specialization, and tooling. JRuby is the most-used alternative Ruby, and with your help we'll continue to make it the best way to run your Ruby apps.

RubyKaigi 2017

00:00:08.880 As of last year, JRuby 1.7 has reached the end of its life. If you want 1.9 support, then you'll have to go elsewhere. We have a JRuby 9.1 branch that supports our Ruby 2/3 initiative, and we anticipate that this branch will last for probably the next year. On the master branch, we're working on JRuby 9.2, which we think is going to come out this year for sure.
00:00:36.030 This release will set the minimum version of Java to Java 8, and we've already started making the required changes. Now, the first section of our talk focuses on three kinds of time.
00:01:00.030 The first kind is startup time, which is how long it takes before you can start executing your user code. The simplest example of this is measuring how long it takes to evaluate your first line of code. The next kind of time is warm-up time. If you use an optimizing runtime like the JVM with JRuby, you might find that your code isn't very fast right away, but after some time, it will reach its peak performance.
00:01:51.060 If you run a server or a long process, peak performance is crucial. If you're running a command-line utility, startup performance matters more. In many cases, we only care about warm-up performance so that people don't post bad benchmarks of us. Comparing C Ruby to JRuby, C Ruby exhibits very little warm-up. Its peak performance is immediate, while JRuby has a slower startup time. However, our peak performance is quite decent, and we want to continue improving it.
00:02:45.130 Currently, we have to load various components during startup, including our internals, the Ruby core library, and typically Ruby gems, which involves loading thousands of Java classes. For example, JRuby 1.7 will load around 6,700 Java classes during startup. Also, since we're a Java program, the JVM itself starts slow and warms up over time, which affects our lexer, parser, and interpreter, slowing down our startup time further.
00:03:51.890 We have explored ahead-of-time compilation in the past and are looking into it again. Kevin Menard will give a talk this afternoon on the Substrate VM, which is another ahead-of-time technology that Oracle is working on. We're also evaluating a commercial project called Excelsior Jet. What ahead-of-time compilation does for a Java program is translate Java to a native executable, thus decreasing the overhead associated with class loading. By providing a test program, Excelsior Jet studies which components can be optimized, putting those optimizations into the executable.
00:04:41.790 In our worst-case scenario with JRuby, executing 'gem list' incurs a lot of compilation because nothing really warms up, and most methods are invoked only once. Loading around 200 gems with default options takes about three and a half seconds. By disabling some optimizations using the '--dev' option, we can reduce this to about two seconds. With Excelsior Jet, we've seen the time drop to about 1.75 seconds, and the Jet team has reported even better performance, so we are examining the reasons for this difference.
00:05:36.910 Moreover, we aim to get a community license for Excelsior Jet, which may allow users to install an optimized JRuby CLI that would replace the existing JRuby executable with a faster startup version. While we find these developments promising, we've learned that JRuby generates many classes after starting, which Jet cannot compile ahead of time. If we can translate those to be pre-generated at runtime, our results should improve.
00:06:07.800 Looking ahead, Jet 13 will improve performance by reducing the loading of infrastructure code when it starts, which we anticipate will help significantly. With those adjustments, we are optimistic about doing 'gem list' in under a second, while MRI does it in about 0.5 seconds, so we are getting close.
00:07:05.310 Next, let's address the warm-up time and how we hope to make it faster. When you load a Ruby method, we convert it into internal Ruby bytecode for interpretation. If the method is called multiple times (around 50), we translate it to Java bytecode, which the JVM interprets. If the code continues to be called frequently, the JVM compiles it down to native code.
00:07:48.890 Though we achieve good peak performance, we do encounter challenges. The issue arises with the additional code generated during warm-ups, which can obscure performance goals.
00:08:01.410 To tackle this, we're working on a new profiler that will help detect 'hot' code and optimize it more effectively. By sending less code to the JIT, we can prioritize what's truly important, enhancing our warm-up performance. We have been focusing on optimizing blocks and numeric methods, where we believe we can achieve significant improvements.
00:09:34.050 As we integrate this new profiling mechanism, we hope to reduce the JIT overhead significantly. Current benchmarks indicate that JRuby can reach up to eight times the performance of C Ruby in certain scenarios. There are still patterns in Ruby, such as dynamic method calls, that we need the JVM to handle better, and our forthcoming work with the Graal JIT should enable us to optimize Ruby operations more effectively.
00:11:20.380 Next, let's turn to our ongoing difficulties with compatibility. All software presents challenges, but Ruby, being extensive, is particularly complex. We aim to accurately reflect our compatibility work without directly criticizing C Ruby.
00:13:41.210 One significant issue we encounter is the lack of complete specifications between the Ruby spec and MRI's internal test suite. For instance, we conduct at least a million expectations and assertions during our tests, yet still manage to receive several bug reports weekly about perceived incompatibilities with C Ruby.
00:16:56.770 A recent example involved the amyl store, where users experienced unexpected behavior within transaction blocks while managing string encodings. We recognized the intricacies that need to be addressed consistently to maintain compatibility. Bugs can occasionally originate from our end, but often they highlight gray areas in Ruby's specifications.
00:18:47.630 Another recent change within Ruby's master branch introduced the inability to return at the top level of scripts. This was initially implemented incorrectly, leading to ensure blocks not executing under certain conditions—definitely an issue that needed resolving.
00:19:53.080 The integration of C code into JRuby presents several challenges, particularly when direct pointers or structures used in MRI have no equivalent in Java. We must navigate the differences between how Ruby handles system calls and how Java abstracts such operations.
00:21:15.270 A significant part of our effort—approximately 90%—is dedicated to compatibility, dealing with edge cases that everyone faces on their path to a stable implementation. We are encouraged that the Ruby 2.4 support is largely done, making way for greater focus on performance improvements.
00:23:09.760 The C extensions frequently present roadblocks, as they can heavily depend on MRI's C implementation. However, we are seeing a growing number of JRuby-compatible extensions developed, and the ecosystem continues to expand.
00:24:56.170 The future direction involves creating a façade API to mirror MRI function calls while actually executing within the JVM, making it easier for developers to port their extensions seamlessly.
00:27:39.820 As for available resources, we currently only have two full-time developers. We appreciate all the support we've received and hope to see further growth in our volunteer and contributor base.
00:28:48.530 For anyone interested in contributing, there's plenty of room across different aspects, whether it's Ruby, Java, or enhancing our documentation and response systems.
00:30:32.950 We encourage Ruby developers to contribute by either fixing bugs directly or experimenting with their Ruby code. Our aim is to gather feedback and continue to improve JRuby for everyone's benefit.
00:32:16.620 Finally, we’d love to hear from you about your experiences with JRuby. This feedback helps us tremendously.
00:32:39.600 Now, do we have any questions? Thank you for your attention.
00:35:03.090 Yes, do we have a few questions? Any questions about the work we’re doing?
00:35:33.140 One answer to a question asked mentioned refinements. We are indeed working on making refinements compatible with JRuby.
00:36:09.030 About collaboration, we've been working together for years and consider it essential to have aligned interests and values. Our shared experiences have enabled us to navigate conflicts smoothly.
00:39:43.600 As for packaging JRuby applications, we recommend packaging them as you would a typical Ruby application, leveraging tools like Warbler, which can create runnable JAR files, or using other solutions suited for JVM.