wroc_love.rb 2013

Topaz Ruby

This video was recorded on http://wrocloverb.com. You should follow us at https://twitter.com/wrocloverb. See you next year!

wroc_love.rb 2013

00:00:18.600 Please welcome Tim.
00:00:26.840 Hi, okay. This talk is going to be about Topaz, which is a pretty new Ruby implementation. I've been working on it with Alex Gainer for the last eleven months or so, but we revealed it six weeks ago.
00:00:33.480 You may or may not know what it is, or you may not clearly understand why we created it. Maybe you've already played with it or even contributed. Regardless, I'm just going to tell you what it is anyway, and if you already know, that's good.
00:00:41.120 So, Topaz is first and foremost a Ruby interpreter. It has a custom bytecode set that it interprets, and it is pretty standard parsing of Ruby, compiling Ruby into bytecodes and interpreting those. That's fine; that's easy, and everybody does that, right?
00:00:55.399 Now, the first interesting thing is that it's written in RPython. This kind of resembles Python but isn't quite Python. I'll get to that later. So, we have this interpreter that's written in a kind of Python, and the cool thing about this kind of Python is that you can translate it into C and then compile it. What that gives you is a pretty fast binary that does a good job at running Ruby.
00:01:08.840 That's awesome! The really cool thing about RPython is that it is actually the implementation language that powers the PyPy Python implementation. If you've ever done Python programming, you may know that PyPy has a very good JIT (Just-In-Time compiler), and we inherit this for free.
00:01:22.600 Now, this JIT isn't actually the same as the one for PyPy, because PyPy doesn't have just this one JIT. You can implement other languages on top of Python and reuse the JIT. When you translate an interpreter into C using RPython, it can actually generate a JIT specifically for your language. It's capable of doing a great job at accelerating exactly your language.
00:01:35.800 So, Ruby code like this, where we run a loop, may be represented in Python code that does a bunch of work trying to figure out what kind of classes we're dealing with. You can already run this Python code on a Python virtual machine and effectively have a real Ruby running on top of a full Python VM.
00:01:53.560 You can translate this to C, but it gets quite lengthy, and I couldn't fit the entire code for this on the screen. This is just the first part of the declarations. However, it also generates JIT information for that method.
00:02:07.560 So, when executing that Ruby code, the Topaz VM will eventually decide, 'Hey, I'm going to trace what you're doing here.' It will realize that you're running this method multiple times, and therefore it will see, 'Oh great! I already have some information about how to speed this up and generate machine code for this.' And it will do that, boosting your Ruby code instantly.
00:02:28.200 There has been a lot of confusion regarding this. While Topaz is written in RPython and while RPython also powers PyPy, it is not a VM that runs both Ruby and Python. There's been confusion on Twitter about whether we suddenly have a VM where Ruby runs on top of Python or if we can mix both languages. That's not the case; it's just a Ruby VM.
00:02:49.680 You might wonder, 'Why another Ruby VM?' We already have so many VMs, and projects like JRuby and Rubinius have taken years to become usable. Why create a new VM?
00:03:01.000 The reason to write a new VM is simply that it's fun and we wanted to explore whether RPython could handle it. Before Topaz, I had never worked on a Python project. I enjoy coding in Ruby and Smalltalk, but Python hasn't been my thing.
00:03:19.080 So, how did I get involved? Eleven months ago, I spoke with K. Fred Boltz, who had just finished his PhD on some metatracing JIT optimizations they do in PyPy. He mentioned that PyPy is a great language for writing interpreters, so I tried it out and wrote a little functional language.
00:03:37.759 I found that it was really easy and quick, and it was already quite fast due to its JIT generation. That's when I thought Ruby could greatly benefit from this technology. I asked Fred if anyone was working on Ruby with PyPy and found out that Alex had committed the first code to Topaz just two weeks earlier.
00:03:53.000 That's how I ended up on the project. As I mentioned earlier, RPython isn't really Python, so I'm technically still not working on a major Python project. I'm still not officially using Python.
00:04:11.480 The reason is that RPython is a restricted subset of Python that is amendable to static analysis, which is necessary if we want to transform it into a language that can be statically compiled, like C. Since RPython isn't clearly defined, the RPython translation toolchain analyzes your entire program: every potential path it might take, and it decides if it can prove enough about the program to convert it to C.
00:04:29.679 This makes it very hard for anyone to tell whether a program is RPython or not, or which parts aren't RPython until you actually try to translate it. In practice, this means that most of the Python standard library is off-limits for you, since it's too dynamic to be analyzed and compiled down to C.
00:04:52.560 I see RPython as a language that looks like Python but serves as a tool for writing interpreters, helping with that process. If you're interested in contributing to Topaz, here's a quick overview of its current structure.
00:05:08.760 There's nothing really exciting like any other interpreter. You have an object space that starts by creating your nil object and true, false, and all these core objects. You also have core classes that are created before running any Ruby code, along with the lexer, parser, compiler, and a bytecode interpreter.
00:05:25.920 One of the areas I plan to expand is the part of Topaz that's already written in Ruby. We found that Topaz does such a good job at optimizing Ruby code that we can write large portions of the core library in Ruby.
00:05:38.919 You might have heard similar claims about the Rubinius project, and we're still at a very early stage. We may eventually find that we have to shift code from Ruby to Python or vice versa. However, we're in a unique position where some code performs faster when implemented in Ruby than if it were in RPython and compiled to C.
00:05:56.840 To summarize our current status: when I prepared these slides a week ago, we had passed around 5,000 Ruby specs, mostly core specs. If you're familiar with the Ruby spec project, those include core, command line, library, and language specs. Of the 5,000 expectations we passed, most are from core and a few from language.
00:06:10.400 The reason we haven't copied the Ruby standard library into our repository yet is that we don't run those specs. Even in core, we're still missing many methods and corner cases. However, the Ruby spec has been extremely helpful in getting us up and running.
00:06:24.160 So, running 5,000 Ruby specs isn't much. For comparison, Rubinius passes about 150,000 expectations. I'm bringing this up to state that we're really fast. You may have heard similar claims from the JRuby and Rubinius teams.
00:06:37.320 What we did was check back with Evan and others from Rubinius and JRuby to find out the hardest problems when implementing Ruby. Those problems typically slow down the implementation. For instance, capturing full binding to execute a binding eval or running an eval in a block context can be expensive for some implementations.
00:06:49.920 We implemented capturing binding with minimal performance impact. The set_trace_func is another feature that can be quite costly. In JRuby, object space isn't even activated by default.
00:07:04.520 We ensured that we implement these Ruby features effectively while still maintaining high speed. However, for proper benchmarks, we still have so much left to cover.
00:07:22.880 We're lacking encoding support, meaning any benchmarks that run on Topaz, especially those involving strings, may skew in our favor since we don't have encoding checking or conversion.
00:07:37.440 As for threads, there's a branch in RPython that might help us add native thread support. However, at this point, we're uncertain about how to do that effectively.
00:07:50.530 Currently, the source tree contains about 15,000 lines of Python and 2,500 lines of Ruby. This should give you a general feel for the size of the project, and I was surprised to find that we have such a notable amount of Ruby compared to Python.
00:08:08.480 Now, I'm going to switch over to demo mode. First, I'm going to run this simple benchmark, which is essentially a while loop and some modulus operations.
00:08:24.400 This will be run on JRuby first, as it is the fastest other implementation I could find. This benchmark takes about four seconds.
00:08:34.360 This specific kind of Ruby code is not something I would typically write. I prefer using 'each' for iteration.
00:08:48.080 Let's see how Topaz performs on this benchmark. I know this may not work because I've reserved some time in this talk to show you how I would implement a method like 'fix_num_div_mod' in Topaz.
00:09:09.960 So the first thing I'll do is figure out where 'div_mod' is implemented. I found 'BigDecimal C Numeric div_mod'. It just returns an array of the quotient and the modulus.
00:09:22.200 So, I have to implement this for 'Numeric'. It turns out that Python already has a built-in function for 'div_mod', so I’ll try and use that.
00:09:38.479 Before I implement it, I could first check that it behaves the same as in Ruby, which it does.
00:09:51.040 While I'm implementing this in RPython, if you clone the Git repository for Topaz, you will notice that the structure contains plenty of less important temporary files.
00:10:04.480 To find class implementations, head to the 'objects' directory. We used to call things 'Fixnums' but changed it to 'Integers' because we hadn’t done the hierarchy properly yet.
00:10:20.760 The implementation for the Fixnum Ruby class is housed within this Python class. It inherits from a Python object.
00:10:36.080 However, on the Ruby side, it looks like Fixnum inherits from Integer, as you may know, while we can have different hierarchies on the Python side for efficiency.
00:10:50.360 Now, let’s take a look at how to implement 'div_mod'. Every Python implementation of a Ruby method takes self as the first argument and typically another argument.
00:11:06.200 This includes the decorator that exposes the method as 'div_mod'. It also incorporates some conveniences for automatic type casting, allowing us to expect 'other' to be an integer.
00:11:22.799 This ensures that whenever the method is called from Ruby, it sends the first argument through this `int` method automatically.
00:11:37.840 Now we can create a Python integer object based on the integer value. However, we want to return a Ruby Integer object.
00:11:49.640 To accomplish this, we'll need to wrap the output of our Python tuple within a Ruby array.
00:12:02.160 You will notice that many operations go through 'object space', which serves as a primary space for referencing objects in Topaz.
00:12:19.920 Let's go back to the benchmark.
00:12:28.200 Because I'm running this in interpreted mode, I may have to exit early.
00:12:39.520 Earlier, I mentioned that you can run the entire Python code on a Python VM. It will be slow, but it's great for testing.
00:12:54.440 Now, let's see if it returns the same result as Ruby. The inspect string is incorrect, but we’ll say that’s acceptable for now.
00:13:05.760 In Ruby, users can pass more than just fixnums; they can also pass bignums, floats, and even infinity. So, I’ll implement this in Ruby instead.
00:13:16.480 This Ruby implementation correctly handles all the intricacies of its behavior. The advantage of implementing in Ruby rather than Python is that you avoid the lengthy analysis and conversion process.
00:13:31.080 Instead, you just write and add the Ruby code directly, which integrates nicely with the compiled version of Topaz.
00:13:46.919 Now let’s re-run the benchmark. While JRuby took about four seconds for a specific case, we're seeing much faster times.
00:14:01.760 The key takeaway is that we’re faster. More importantly, Ruby code is not slower than the Java code.
00:14:12.360 Let's take a glance at how the JIT functions. I'll generate a log by setting the PIP_LOG environment variable.
00:14:26.840 When I run Topaz again, it'll produce this log, capturing various operations and their statistics.
00:14:48.120 In this JIT execution, we can observe that in every loop, it's assessing a local variable's state. Post-JIT, it checks whether this local variable remains an integer.
00:15:06.760 The JIT can optimize the condition checks, leading to lesser overhead as the process becomes more straightforward.
00:15:21.920 The optimization sheds unnecessary operations as it identifies that we aren't interacting with certain elements, like the block, allowing for direct execution.
00:15:37.240 The JIT generates very efficient integer operations to streamline processes, resulting in performance advantages in this instance.
00:15:52.080 This quick overview shows how Topaz can deliver significant improvements to Ruby performance.
00:16:06.240 From this presentation, you've learned that we strive to be the fastest Ruby implementation. Even as we implement more features and size up against Rails, we aim to maintain this speed.
00:16:14.760 However, we will need help to prove that Topaz is indeed faster compared to other Ruby versions. If you're interested in Ruby VM development or if you encounter issues, please consider contributing.
00:16:30.560 We encourage you to engage with our community on the Topaz Freenode channel and follow the Topaz project for updates. Here are the links to the git repository and the website.
00:16:51.680 My Twitter account and IRC name are listed here, so feel free to reach out if you want to discuss Topaz further.
00:17:06.800 Thank you.
00:17:11.400 Thank you very much! Any questions? We have two more minutes.
00:17:21.639 It seems like Rubinius might be a good source of code for Topaz. What do you think about the time and effort it might take to port code from Rubinius over?
00:17:43.680 The core source code in the kernel has three subdirectories: core, common, and delta. Initially, the idea was to share code with Rubinius by avoiding Ruby-specific code.
00:18:03.680 When examining Rubinius primitives, you'll notice many files still contain Rubinius-specific code. We will copy the necessary files over to be functional.
00:18:20.480 Having discussions with the Rubinius team shows they're still interested in sharing code, which we see as an opportunity for collaboration.
00:18:36.640 Regarding benchmarks, I would not recommend using wall-clock time comparisons for performance measurement in a JIT VM.
00:18:48.600 I believe Topaz will be the fastest Ruby implementation as a Ruby programmer. However, I'd advise using operations per second instead, which allows for better warm-up of VMs.
00:19:04.000 How does Topaz perform over time, especially when dealing with varying request patterns on large applications?
00:19:13.000 The JIT never stops monitoring execution. If you run a million loops and experience a pattern change, it will continue optimizing new operations.
00:19:25.280 Could you revert to the slide showcasing the hard problems faced? It appears many of these challenges are universal among Ruby implementations.
00:19:36.480 Indeed, solving these isn't just difficult for Topaz but for any Ruby implementations aiming to be performant.
00:19:57.520 Finding balance between a foreign function interface and retaining speed can be tricky. JRuby has deprecated C extension interfaces due to performance challenges.
00:20:14.250 Asking Ruby developers to rewrite their C extensions for Topaz is quite a tall order. That idea was at the core of Ruby's FFI, but it remains tricky.
00:20:31.760 What has been the most valuable takeaway from working on Topaz?
00:20:47.600 I learned to trust the power of metatracing.
00:21:04.240 Many have shared that MRI has hard-to-understand documentation. Did you face challenges that led you to read source code to comprehend certain behaviors?
00:21:13.600 Yes, indeed. Many aspects of MRI are poorly documented, and its source can be quite hard to navigate. However, I found Rubinius's code very well documented, clarifying many behaviors.
00:21:29.680 I heavily relied on work done by the Rubinius team, especially regarding Ruby spec.
00:21:46.560 Is Topaz aiming to remain relevant to Ruby 1.9 or 1.8 features, or is the focus solidly on 2.0?
00:21:59.640 For the time being, we're concentrating on 1.9.
00:22:08.480 One last comment about benchmarks: I found the example benchmark somewhat too artificial.
00:22:22.760 Optimizers in compilers often conduct computations at compile time, leading to misleading metrics. A different benchmark approach could yield varied results.
00:22:37.600 I'd like to see benchmarks that utilize diverse input to gauge accuracy under realistic conditions. However, I did experience instances where PyPy lost to Python depending on the scenario.
00:22:52.000 To clarify, the previous benchmark wasn't designed to be comprehensive. It was too simplistic. I simply wanted to keep it brief for the talk while demonstrating the jit log.
00:23:05.680 Nevertheless, there's a benchmark suite called 'Meta Tracing VM Experiment' on GitHub, created by Lawrence Tratt, which involves extensive benchmarks using external input. I'm attempting to run these benchmarks on Topaz.
00:23:23.680 So far, any that currently execute on Topaz outperform JRuby, with the exception of a simplistic Fibonacci example.
00:23:40.960 I aim to ensure all these run effectively and efficiently on our Ruby implementation.
00:23:54.080 Thank you for your questions.