RubyKaigi 2018

JRuby 9.2 and Rails 5.x

JRuby 9.2 has been released. 9.2 supports Ruby 2.5 compatibility and it also runs Rails 5.x well. This talk will discuss some of the more interesting apects of JRuby 9.2:

- Performance updates
- Graal integration
- IR instr refactoring
- Object shaping
- Full encoding support ( @@γ‹γ„γŽ ||= $🐻🌻.send :β”¬β”€β”¬γƒŽΒΊ_ΒΊγƒŽ' )
- Improved Windows support

It will also give an update on the state of running Rails 5.x on JRuby. This talk will go over updates we have made to ActiveRecord-JDBC and show a real world use-case of getting Discourse running. Get up to date on the state of JRuby!

RubyKaigi 2018 https://rubykaigi.org/2018/presentations/tom_enebo

RubyKaigi 2018

00:00:04.520 My name is Thomas Enebo, and I work for Red Hat.
00:00:19.199 I've been using Java basically since it started and have been using Ruby since 2002. That's also when I started working on JRuby.
00:00:25.329 This is the roadmap from last September at the last JRuby Kaigi. I'm just going to go over what has happened since then.
00:00:38.230 We were going to retire JRuby 1.7, and we did. A couple of people asked us to keep it going, but most were happy to move to JRuby 1.9.4.
00:00:44.140 Two weeks ago, we put out JRuby 9.1.17. I think we've put out four point releases since the last year, and last week we released JRuby 9.2.
00:01:00.400 It has Ruby 2.5 support. You'll notice in our roadmap that we didn't put out a 2.4 release; we can say that this was due to procrastination.
00:01:06.280 We started our 2.4 branch in October of 2016, a couple of months before CRuby released 2.4. By May, we were about 90% complete, but by September at Ruby Kaigi, we were still about 90% complete. We realized we needed to get moving.
00:01:21.250 Then by December, we were nearly ready, but we realized that CRuby was releasing 2.5, and we were only putting out 2.4, which seemed kind of weird. So, we decided to push on and support Ruby 2.5 instead, skipping 2.4.
00:01:39.130 There are really only two big bullet points for JRuby 9.2. The first is the support for Ruby 2.5, and the second is non-ASCII identifiers.
00:01:46.810 When we try to add a new language level of support, we'll create a new issue on GitHub, and it typically generates lots of checkboxes. We usually build this list from MRI's news file, but we noticed something strange: we get a lot of new contributors whenever we make one of these lists.
00:02:05.170 It seems they look through and adopt the tasks they want to work on. However, if we created many individual issues for each feature, we wouldn't garner as many contributors. This is somewhat of a mystery.
00:02:16.900 We've also noticed that fewer people ask us for commitments; they're generally happy just to submit pull requests. We're continually thinking about how to attract new contributors.
00:02:24.700 Another major feature of JRuby 9.2 is the full support for any encoding of identifiers, including local variables and constants. We partially supported this in previous releases, but there were various quirky issues.
00:02:32.920 Here, we define a method called table_flip that prints out a message. Then we call this method. Using reflective calls to get a list of all the methods on the type should include table_flip.
00:02:48.890 However, it just crashes because those bytes coming from the table_flip method are garbage. In JRuby 9.2, we've fixed this issue.
00:02:56.200 Another issue users encountered was calling a method with multi-byte characters. Sometimes the method wouldn't exist due to a typo, which led to undefined local variable errors.
00:03:07.770 In JRuby 9.2, we handle these discrepancies; however, the user experience has been quite frustrating.
00:03:14.540 Allow me to explain why it has been challenging to tackle this issue for the past ten years. The short answer is that we store and key off all methods and constants using Java Strings for performance reasons.
00:03:30.640 Unfortunately, Java strings can represent different encodings, yet they have no way of reporting what encoding they're storing. Fixing this issue required approximately 5,000 lines of code changes.
00:03:42.230 Previously, we took identifiers like table_flip, parsed them into bytes, created a Java string from these bytes, and used that as a key to store our method definition.
00:03:57.220 When we called methods later, we would ask the type for all the keys. The problem arose when some keys had an unknown encoding, leading to the mistakes seen earlier.
00:04:04.300 The solution was to construct a Ruby symbol from the bytes. The string we made is either a raw or binary string, ensuring that every byte in the Ruby string is a single character in the Java string.
00:04:12.420 We save our symbols to the symbol table using this key and then use that raw string to store our methods. Now, when we retrieve our keys, we can look up something properly encoded, and we can print everything out.
00:04:26.460 Although everything is almost perfect, CRuby allows for two symbols with the same byte sequence but differing encodings, which is one area where we aren't quite perfect.
00:04:33.300 However, this situation is relatively rare, particularly if you have two symbols that fit into ASCII; in that case, we just change their encoding to US-ASCII.
00:04:47.030 Now, let's switch gears and discuss performance.
00:04:52.290 The first topic is Graal, a new JIT compiler. Although it has been in development for several years, it is now starting to make its mark. Graal is written in Java and is bundled with OpenJDK 9 and higher, but it isn't enabled by default and requires a specific option to activate.
00:05:00.840 In the bytecode interpretation process, if the code is deemed hot and needs compilation, it first goes to the client compiler (C1), which performs fast optimizations. If it continues to run longer, it escalates to the server compiler (C2), which has more type information and can generate better code.
00:05:07.840 By using the JVM compiler interface, we can direct the system to use Graal instead of C2. So far, our experience indicates that benchmarks are sometimes much faster with Graal than C2, but usually, performance is similar or slightly slower.
00:05:19.750 The faster results are attributed to partial escape analysis, where Graal excels compared to prior methods.
00:05:29.950 We face a math problem in JRuby. Whenever we execute something like '12 + 2', we must construct Ruby Fixnum objects for each number, which leads to performance overhead.
00:05:37.800 Graal's escape analysis helps here, as it can determine an object's existence within a particular scope and optimize the instantiation of Ruby Fixnum objects.
00:05:48.480 For example, if we proved the identity of an object and that it is used only for reading a specific field, the compiler may choose to directly operate on the extracted value, thus allowing faster execution.
00:06:03.820 This is evident in benchmarks, as we strive to ensure JRuby performs comparably to CRuby. In default mode, JRuby tends to perform slightly better than CRuby, especially with invokedynamic support enabled.
00:06:20.510 Using Graal improves performance as it handles the instantiation of Ruby Fixnum objects more efficiently. Disabling the cache for common Ruby Fixnum objects led to noticeable speed gains.
00:06:31.200 Currently, JRuby matches or slightly underperforms compared to competing implementations, which is promising.
00:06:39.190 Moving on to optimizations, we have recently implemented call frame splitting, where we store additional information when invoking methods or blocks.
00:06:49.140 Previously, if we were dealing with regular expressions, we would store that data, allowing special methods to determine if a block was passed into the call, enhancing performance.
00:07:03.490 The cost of saving these additional fields isn't free and becomes apparent when calling trivial methods.
00:07:10.370 In benchmarks, we can see significant differences in performance, particularly for operations like array indexing.
00:07:22.220 An additional optimization we tackled is storing keyword arguments directly into a Ruby hash, instead of an ordinary Ruby hash. This has led to performance improvements, reducing the overhead when using keyword arguments.
00:07:37.380 Going forward, our goal is to convert keyword arguments into special positional arguments, eliminating Ruby hash allocation altogether, and achieving better performance.
00:07:47.320 Now, let's talk about JRuby on Rails. We've been running JRuby on Rails since 2006 and have many users in production.
00:07:55.600 However, with the transition to Rails 5, we fell behind in compatibility with Active Record. This was primarily due to the JDBC library used for database access.
00:08:10.610 Maintaining compatibility is a challenging task. Active Record JDBC must work with multiple JRuby versions (currently 1.9 and 9.2). We also face differences between Ruby and JRuby, especially since we run on several versions of Java.
00:08:25.350 Our last release before adding Rails 5 support was Active Record JDBC 1.3, targeting various JRuby versions. Supporting multiple active record versions complicated our code base, leading to many checks littering the implementation.
00:08:33.680 Back in 2008, we had more contributors on projects like this, but nowadays fewer people contribute, making the integration difficult. As I began the transition work for Rails 5, I frequently encountered lines of code that puzzled me, questioning their relevance.
00:08:49.711 Essentially, to avoid the mistakes we made in JRuby 1.7, where we mixed functionalities for various Ruby versions and ended up with complicated nested conditions, we opted for a strategy where each branch supports a single version of Active Record.
00:09:07.790 Our new structure means active record version 5.0 is supported by one version and 5.1 by another. We're also limiting support to the main three databases Rails supports.
00:09:18.590 When Rails 5.0 reaches the end of life, we will stop supporting that branch. Since this overhaul, we have significantly decreased our codebase complexity, reducing our lines of Ruby from 1,600 down to 280.
00:09:36.190 With the updates, we removed unnecessary version checks and leveraged the core Active Record code more effectively, cutting down outdated checks in Java code, albeit keeping the vital checks intact.
00:09:49.810 We noticed improvements when updating to Active Record 5.1 and capabilities to add lightweight methods, streamlining the code base further.
00:10:05.911 As we proceed, we aim for a more robust testing state with Rails 5.1, which currently has only 36 failures among about 800,000 assertions.
00:10:19.300 Most of these failures appear linked to specific bugs, particularly with precision issues. Recently, we've explored Rails 5.2 support.
00:10:27.090 While testing action cable, it has not bootstrapped adequately. We're looking into it as we prepare presentations on JRuby's capabilities.
00:10:39.900 Currently working with SQLite for testing action cable, we face eight failures. However, aside from minor setbacks, we're able to execute scaffolded applications.
00:10:46.820 The main obstruction is related to configuring the testing environment, particularly concerning the fork feature, which JRuby does not support.
00:11:01.750 To ensure compatibility with Rails 5.2, some tweaks might be necessary, especially in the gem file. We continuously strive for seamless integration and functionality with the latest Rails versions.
00:11:15.610 Lately, I've been focusing on performance benchmarks, analyzing our performance regression through an internal benchmarking suite with comparisons to CRuby.
00:11:29.660 For instance, I benchmarked the performance of creating 5,000 rows with just a string column and observed that lower times are preferred, showing CRuby outperforming various JRuby modes.
00:11:39.000 Although CRuby currently leads, we’ve seen JRuby's performance improve significantly compared to previous years, where the performance gap was notably wider.
00:11:48.400 One key area of improvement lies with the transcoding of database queries between UTF-8 in databases and Ruby, where extra transcoding previously slowed JRuby down.
00:11:58.600 We've managed to optimize this process, with RUBY 2.5 now showing considerable performance improvements due to these optimizations.
00:12:09.800 As we compare performance for operations like selecting data or updates, we find JRuby generally holds its own against CRuby and shows great promise for future enhancements.
00:12:21.750 Looking to the future, we intend to simplify our integration with core Rails to ensure Ruby code alignments, as involvement in core development remains a priority.
00:12:36.200 Particularly as JRuby continues to evolve, we aim to minimize disruption in Rails compatibility and actively work with contributors to improve the development process.
00:12:47.250 We want to embrace projects like Discourse and involve ourselves in their development to showcase *Ruby 3x3* and seamlessly integrate JRuby.
00:13:03.570 Considering native C extensions, there are ups and downs. Occasionally, we face new C extensions that need to be integrated into JRuby, creating a cycle of adaptation and resolution.
00:13:10.970 For certain algorithms like cpp-gi, which lacks a Java equivalent, we can script Java types as Ruby code, making workflow smoother.
00:13:29.190 For issues pertaining to libraries lacking Java counterparts, like *no-co-gumbo*, we can rely on bindings to Lib no-co-gumbo.
00:13:35.497 We also work on porting the JSON library into JRuby, which almost reaches fruition. Being close to completion means we only need a few more refinements.
00:13:47.799 By collaborating with authors and optimizing interactions with native extensions, we aim for better integrations and experiences for developers seeking to use JRuby.
00:14:02.900 Despite my speed during this talk, I appreciate the opportunity to share our advances and performance enhancements with everyone. Thank you all for being here.
00:14:15.060 If anyone has questions, feel free to turn on the mic and ask. I'll do my best to respond before time runs out.
00:14:23.760 One question asked concerns the relationship between JRuby and Truffle Ruby, which is more indirect.
00:14:32.050 At one point, our codebases were unified before they branched out due to not sharing as much code. Ruby Truffle borrows extensively from JRuby's libraries, such as Joani and JCodings.
00:14:45.900 We also use components from Rubinius, connecting our development paths more closely. Moreover, Graal, the new JIT compiler, is under Truffle's umbrella.
00:15:03.000 As a result, we foresee opportunities for future collaboration and integration of technologies among different Ruby dialects.
00:15:17.230 The roadmap for JRuby looks promising, with exciting developments focused on compatibility enhancements with core libraries, especially in light of upcoming Ruby versions.
00:15:31.600 Any changes made towards the JRuby runtime will benefit performance overall, particularly with regard to keyword arguments and method dispatching.
00:15:44.560 Thank you again for your time, and if you have further questions, feel free to reach out.