LLVM-based JIT compiler for MRI
Kokubun, Takashi
JIT compiler is considered a promising approach to achieve Ruby 3x3 goal these days. But the direction of its implementation is not fixed yet. What will be the ideal approach to achieve that goal? In this talk, seeing my experiment to implement LLVM-based JIT compiler, you'll know how native code can be compiled from YARV ISeq, what makes it fast in LLVM, what are the difficulties underlying in it and how to solve them. I hope a discussion in this talk will help Ruby 3 to be great.
 Today I'll talk about just-in-time compiler, whole MRI or CRB. And I'm Takashi Kokubin, so let me introduce myself first. Last year of the week, there are some similar people from Japanese. People are confused, who is Matt or Koichi or me. They are wearing grasses or black foodies. And so this year I'm wearing very convenient t-shirts that has icons. So, yeah, nice to meet you. This is my icon, so please remember me with this. Even if I attended last year and I met some people, and this year some people said to me, nice to meet you, but some of you guys I met before. And so one of them, my project is the Hamlet. This is the original 8x faster version of Hamlet. So I'm interested in template engine optimization. Then I joined Hamlet organization. And this year I optimized Hamlet 4x faster. Yeah. And then joined the Ruby organization and ERB maintainer. Then I made ERB 2x faster. This will be shifting to 2.5. So I love fast things and making things faster. And I love Ruby language. So why not I optimized Ruby? So it's time to introduce Jet Compire. I did this. The right one is the Jet Compire I made this year. So this talk focuses on how I achieve this. Before that, probably many of you have no experience to develop Jet Compire. So I introduced what's Jet Compire. Jet is the abbreviation of just in time. And Jet Compire optimizes the program by comparing the program to native code. But probably many of you guys have no experience to compile to native code. So I will show the difference between native code and byte code. And so example method is that this is just three, multiply three methods. And it's passed to this kind of tree. And it's compared to this kind of thing. So on the right side one appears frequently. So please remember this. It means put three and put three again. And multiply operation and then give means return. So it's very simple. And Ruby VM can understand the right side code as the byte code. Bite code means the native and VMs language. So Ruby VM is stack-based instruction. No, no, no. Stack-based VM. So when Ruby VM interprets the byte code, it's rows the instruction one by one. And it's pushed to stack when the put instruction is read. And then three is pushed to stack again. And when it reaches the operation of much, right, it's calculated to nine. And then it's returned in leave. From the VM evaluation method returns the nine. So how is it different? Is it different from JIT compiler's version? And with JIT compiler normally it has another thread for JIT compiler. And from the initial, it interprets like the former way. It reaches the code count. Method code count is increased. JIT compiler detects it as the hotspot. And JIT compiler compiles the hotspot to native code. Then VMs switch to just code and native code instead of dispatching instructions by their self. So the byte code is the actual code of Ruby. And after JIT generates the native code from byte code, Ruby VM starts to call native code. So it's the basic of JIT compiler. So if the native code is faster than evaluating byte code one by one, it becomes faster. But why this kind of complex thing should be introduced to Ruby? So first of all, I want to make things faster. The reason is one of the reasons is money because fast means that it requires less computer resources. So your number of servers can be decreased if the Ruby is optimized. And also, in this experience, if Ruby becomes two times faster, probably your application becomes two times faster. So if the application is faster, probably user experience is improved. And also, to always use Ruby. This year, I developed distributed Q middleware with Java because of the parallelism and the performance. But I want to use Ruby in ideal situation because I just love Ruby. So then another criticism is that just in time versus ahead of time, the characteristics of the JIT compiler is the JIT compiler's program during the execution of the method itself. And you may think currently, byte code is compiled before the execution, but JIT does not compile the program before execution. But the AOT compiles the method to native code before the execution. So why not AOT for this purpose? One of the reasons is the keep boot time shorter because the optimizing method to native code is very time consuming. And if the compilation is asynchronous, compilation time can be skipped. So boot time or some one-shot task can be very fast. And another reason is that some optimization requires the runtime information because Ruby is very dynamic. So many things can't be known until it's executed. So we can use the runtime information for that kind of optimization. So then another reason is there is working kind of vectoring implementations and that achieved better performance. So there are some promising candidates of the JIT compiles. And the right side one, Mjit is the Vladimir's JIT compiler. And the one left side that you have, Mjit is one I created. So they are very faster than Ruby 2.5. It's the current trunk, most of the trunk. And the two of the JIT compiles doubles the performance. So it's worth merging to the trunk maybe. So I want to explain the mechanisms as much as possible. There are three implementations I want to introduce here. Two of them are made by me. In a half year, I developed two different JIT compiles. First one, Eril Abbey. Thank you. The original title of this talk was the Eril VMS JIT compiler. But after the VKG finishes, I developed another JIT compiler called FMJIT. So this talk title is changed. The middle one, Mjit is developed by Vladimir and introduced by Matt in the first keynote. So I introduced one of them. Oh, sorry, three of them. OK, there. So Eril Abbey is motivated by the RubyKai 2015 keynote. It's presented by Evan Phoenix. It's very impressive. And I very like the talk. And its essence was the compiling the Ruby core by CRAN to Eril VMS code and generate Eril VMS from your byte code. Then compare them all together. So if you do that, we can inline Ruby core's methods to Ruby's methods and very optimized. And I made it possible with Eril Abbey. Then I introduced how it works. So when it interprets the byte code frequently, Eril Abbey detects the hotspot by StackProf like profiler. And when it's detected as hotspot, JIT compiles the methods to Eril VM. Not good point is that it's not different thread. As it's experimental project, it compiles the same thread. And there is a precompiled Eril VMS code from Ruby's core methods. So it's the, and the Eril VMS uses both of the Eril VMS. Then it's optimized with those Eril VMS to native code. Then switch from a byte code to a native code. So how is its performance? Is this well designed for this implementation benchmark? Was about five times faster. Why is it fast anyway? So it is the trick of the Eril VMS optimization is called Eril VMS pass. Eril VMS pass is a kind of framework to analyze and optimize Eril VMS. And many of the optimization of CRAN compiler is done in Eril VMS pass. It's kind of rack and middlewares. So, and it takes Eril VMS IAL and outputs Eril VMS. So we can organize many Eril VMS pass you want. Then probably you can't read them, but they are the Eril VMS pass used in Eril RB. And affected their performance. But I could reproduce the same five times faster performance with only two passes. So let's see how they contribute to performance. So it would be the secret of Eril RB's optimization. The summary, just compiling the code to native code is not makes it faster. Because just comparing to native code means just removing the dispatch of instructions. Bite code dispatch was the go to, just go to, leaving a pointer of Ravell from some array and go to some other in function. Some other in function. It's not so optimizing. Then if we add the function inlining pass to this pipeline, it's optimized well. And Eril CM pass is do invariant code in motion also has the major impact. But as you can see, inlining function is the very important thing for optimizing native code. So I learned that thing from this Eril RB project. But learnings from Eril RB is that Eril VM is hard to develop with. And also, Eril RB works with Eril VM 4.0, but it does not work with Eril VM 5.0. So it's very breaking, yeah. And also debugging is very important to maintain program for a long time. But Eril VM is, of course, it's not C language. So if we, when we do debugging with GDB, we can see C code for native code. Because it's in Eril VM. So it will be harder to debug. So I wanted to tackle another version. It's built with C language. So yeah, important learnings from this is that Ruby, main optimization target of Eril RB is Ruby cores methods. So directory generating the Eril VM IR was not important or didn't have major impact for optimization. So using CRAN would be enough for optimizing C language code of MRI. So I changed the approach. Then before I introduce another one, I want to introduce MGIT for the version of Matz Keynote. MGIT is developed by Vladimir Makarov. He's GCC main term. And MGIT project consists of two components. It's just not JIT compiler. It has VM instruction replacement and also MGIT method JIT in compiler. VM instructions are kind of put object or leave. All of them were replaced in this project. And as you were seeing, YARB is currently stack-based VM like Java. But this project replaces it to register-based language instructions. And another point I want to notice is this dynamic bytecode specialization. It also does static bytecode specializations but also dynamic ones. When method is executed or evaluated in VM, it changes the bytecode to another instruction. Like this example is INDSET. INDSET is an RF reference as known set. And the set has the version of array version or and the argument is integer version. So many specialized versions are existing in MGIT. And it's also it will be it will contribute to the JIT performance because if the code is real small can be smaller. In the version can be, of course, many optimized real. So it's good for JIT compiler. And background, the method difference between error and image is that it's compared in background thread with pre-thread. So it's currently not portable to Windows. But the important thing is that it's compared as synchronously. So it's good performance. And it also has the optimization. You may not know, but during the intermediate states of JIT's calling, it can fall back to bytecode by restoring the state of original VM states. So let's see how it works. Here's original version, current version of MRI. And it's depressed like this. The right side's instructions are MGIT's original out-year instructions. And VM has infinite number of registers. And MGIT reads the instruction and assigns the values to register instead of stack. And when during execution, it specializes the instruction by the receiver or arguments type. It's good for inlining the definitions in the JIT compiler. Then register instruction returns the value from register. It's different from the current behavior. And MGIT interprets the, during the interprets, bytecode, and the thread compiles the bytecode to C code. It's the very kind of invention in MGIT. It writes the C code to disk directory. And it's let's GCC or CRAN, the C compiler, binary, or the JIT compiler executes. X calls exec to execute the compiler. And it compiles to SO file. And the DLC function allows us to roll the native code as C function pointer. So we can call that C function from VM instead of evaluating the bytecode. So how is MGIT? MGIT achieved great performance. Very great. It claims three times faster from Ruby 2.4. It was not true in my machine, but kind of very fast. And replacing, but also it replaces the compiler to generate another instruction sets. And also VM instruction. So replacing VM instruction at all might be very risky. So I thought I want to fix this. And I focused this. Yeah, MGIT. Yeah. This is the focus of MGIT project. And this project is currently I'm working on. And yeah. And the major characteristic is that it achieves JIT comparison without changing VM instructions at all. So it simulates stack on comparison and does not require register-based instructions. So let's see how MGIT optimizes this. Okay. I understood. So before seeing how JIT compiler optimizes the method, how would you optimize this method in VM level? You may think this can be possible. Like just putting nine from initial. But consider this method. This is the definition allows us to do this kind of three, multiply three equals to three somehow. So this optimization can't be done in not just impossible, but it's hard. And currently YARB does not optimize in this way. But in YARB MGIT, during its interpretation, JIT compiler checks the core count and generates C code in like this. Probably it's hard to see, but just doing automates is called with three arguments. And also, important thing is that it has the definition of automates. It is for inline in the future. Because I learned inlining is important in LRB. I made sure that it's inline in this version. So C compiler compiles this source code. And then if the method definition of automates is included in this source code, it's inline in this way in C compiler. And then as three and three is provided in this source code, the three multiplied three is calculated beforehand before execution. And also, notable thing is that it checks the method definition properly. So it really works. And we can return the result. Optimized result, it's optimized by C compiler. I want to say that this is not optimized by our work, but it's optimized by other C compiler's efforts. So optimizing code would be very complex thing, but we can delegate such hard work to see very good other things. Then it's loaded as, so file and the DLC into native code and call this one. So we can return the optimized nine, just nine, without calculating the multiply. Of course, it's, we probably, such optimization wouldn't be needed just if just it's a multiply calculation. But another very complex version of the optimization can be provided because we don't do some specific optimization in ourselves. So basic ideas to optimize the MGAID is the inlining functions and skip unnecessary work and decrease in comparison time. And so I introduce secrets of MGAID's optimizations. Many of the following sites are in only text. So you may be sleepy, but please ask me questions later. The first one is inlining function by having VM source code as header. MGAID originally do this by compiling VM.c beforehand and compiling not fully compiled, but just preprocessing header include or define or things like that. So by having those VM instructions, many of the frequently used methods in VM instructions, we can inline those methods. And then transform functions in header static. As the VM.c compiler header can be dealt with by our script, we can modify this header by our Ruby script. And we as the static to the non-static functions. And if the function is static, c compiler can know it is not necessary to be compiled. So comparison is skipped for those static functions. And another technique is skip set jump. You may not know set jump, but it is used for implementing exception. Exception is implemented with set jump and jump. And of course, it needs to store some states of the VM, so it is very slow. So it skips it. If the method does not seem to raise exception. So of course, we need to check it may not raise exception, but currently YARB-MJIT does not check it properly. So I will fix it right here. And about inline ingarage function, it is for just decreasing comparison time. When I developed YARB-MJIT in the first, its comparison is very slow, like 500 milliseconds for each method. But if it takes 500 milliseconds for one method comparison, very limited number of methods can be compiled during the benchmark. So Octogal score was very small in that state. So VM search method is the function that searches the method. So it is very complex things, because Ruby is very complex about Ruby methods. So it is very slow to compile. So YARB-MJIT also implements the optimization to skip that execution. And then very complicated thing is the base pointer. I can't explain this precisely. Because the JIT code is executed frequently, only JIT compiler compiles hotspots. So all of the methods compiled one is the hotspot. So all of the very small tasks would have impact. And we can calculate the base pointer is needed to restore the state before the JIT run. Please ask this later. And the last two ones are inline external functions using VM sessions. Some of the functions in instances.dev is not static run. And it's linked as the external functions. So as the JIT header is compiled from VM.c, functions not in VM.c are not available for VM.jit compiler. So I added some of the methods definitions to header by some efforts. But Mjit does this by dynamic bytecode specialization. Because it replaces the instructions to some specialised ones, specialised ones has the definitions. So Mjit can inline those things. So it's faster. And the last one is the inline set of methods code. This is using Lantain information. And Ruby VM has the call cache in bytecode. bytecode has its inline cache. Call cache is used to implement inline method cache. Inline method cache is the only use for the one place. Sorry. It's very hard to explain. But it has the definition of the method. And the cache is created during the execution. So we can use the call cache to detect which type is used for Ruby method. So there are many types of Ruby methods like implemented in Ruby and implemented in C function or ADS or attribute reader or things like that. If we detect those types, we can inline the set of methods code. And this optimization had a large impact. Then with those techniques, YARB Mjit achieved this very good performance compared to current trunk. So future works of YARB Mjit optimizations are both more techniques from Mjit because it's folk. Removing unnecessary functions in per combined header is not implemented yet because it depends on some RTA code. Also I want to, I think I can inline core methods defining C function because we can detect the type in call cache. And we, probably we can know the which function can be used for the Ruby C function defined method code. And probably we can inline a method defined with byte code by using the detecting it by call cache. So no, no, no, no, no. It needs to save some last compiled some gt code. If we store some gtc functions, previous functions, we can use it to refer to the method. So I think method defined with byte code can be inline. The main part of this talk is how can we realize JIT for MRI because this is conference. So many people have failed to introduce JIT. There were some talks about JIT compiler in RubyKaigi. So LRV is very similar or uses that same technique with YARB to LRVM. It's very old project and we have failed from such old age. Also there was promising JIT compiler, tracing based JIT compiler, but also it's not merged to Ruby core for now. And also Ruby news, probably you may know Ruby news uses LRVM and it has JIT compiler. I studied, I read the previous code before I implement LRV, but actually it didn't have JIT compiler in 3.0 because they said JIT compiler is hard to maintain and it has many bugs. So it's very difficult thing to introduce JIT. But we should solve those problems. Why did they fail? YARB to LRVM was how to improve performance with MRI time because probably it's very old. So the VM are different. And Ruby was memory consuming so it's not merged. But MZT solved this part well. Also Ruby news was how to fix bugs in JIT. So let's see one by one those problems. Can we solve improved performance? So MZT achieved the performance and I did improved performance. And also many of you good people, Endosand and Noa Gibbs made very good benchmarks. So thank you. And so it's very good for improving performance. And memory consuming MZT solved this problem too and how to fix bugs is very large problem. So MZT does not use LRVM directory. It can be used by a crank but it doesn't use LRVM directory. And so no need to keep up with breaking changes. And we can debug using C code using GDB. So very easy. Very so easier to debug in C code. Because as VM code is written in C, we can debug is only seeing C codes. But it needs many changes for interaction. And the very normal thing is the rest you change, the more unlikely it will be hub bugs. So I want to reduce the changes. And I want to say in this talk make initial release as safe as possible. Because we are using Ruby in production. And also my application in my company is very serious to want to be, it's very, I want to make it to be very stable. So I don't want to introduce very breaking change to Ruby. So don't solve many difficult problems at the same time. So MVGIT does replacing VM instructions, all of the instructions, and introduce this compiler, but it's not portable. So we need to do two different very difficult problems. And I want to make it optional at first. But we can't turn off replacing VM instructions because byte code is compiled beforehand. And unlike MVGIT, it can't be turned off. So if the only MVGIT is introduced, we can turn off the JIT compiler by just stop to pass the option. So even if we found the problem in JIT compiler, we can turn off. So it's very safe. And also I want to change it gradually. So big one release is very risky. And it's hard to develop. It's also hard to develop. Because releasing may face conflicts, many conflicts. I want to develop in gradually. So this is the current status of those JIT projects. MVGIT is not slower than Ruby 2.0, but it's actually slower than current Ruby trunk. But YARV-MGIT does not modify VM, so it's not slower. And another thing is that YARV-MGIT does not modify VM instructions. So it passes all the tests in Ruby core without JIT. But MVGIT replaces instructions, and it fails with current tests and Ruby spec. So I think YARV-MGIT is very safer. So we need your help. Please try and report bugs to those repositories. Reporting bugs is very helpful to develop. And one of the Ruby committers is reporting bugs to YARV-MGIT and it's helpful. So I write this, how to use YARV-MGIT, so read this. I published this. Conclusion. Methods compare for MRA can gain good performance by inlining. And YARV-MGIT is a safe migration path to realize realization. I want to realize JIT compiler. To realize it, bug reports using Minerary are very helpful. That's all. Thank you. Thank you.
 Today I'll talk about just-in-time compiler, whole MRI or CRB.  And I'm Takashi Kokubin, so let me introduce myself first.  Last year of the week, there are some similar people from Japanese.  People are confused, who is Matt or Koichi or me.  They are wearing grasses or black foodies.  And so this year I'm wearing very convenient t-shirts that has icons.  So, yeah, nice to meet you.  This is my icon, so please remember me with this.  Even if I attended last year and I met some people, and this year some people said to me,  nice to meet you, but some of you guys I met before.  And so one of them, my project is the Hamlet.  This is the original 8x faster version of Hamlet.  So I'm interested in template engine optimization.  Then I joined Hamlet organization.  And this year I optimized Hamlet 4x faster.  Yeah.  And then joined the Ruby organization and ERB maintainer.  Then I made ERB 2x faster.  This will be shifting to 2.5.  So I love fast things and making things faster.  And I love Ruby language.  So why not I optimized Ruby?  So it's time to introduce Jet Compire.  I did this.  The right one is the Jet Compire I made this year.  So this talk focuses on how I achieve this.  Before that, probably many of you have no experience to develop Jet Compire.  So I introduced what's Jet Compire.  Jet is the abbreviation of just in time.  And Jet Compire optimizes the program by comparing the program to native code.  But probably many of you guys have no experience to compile to native code.  So I will show the difference between native code and byte code.  And so example method is that this is just three, multiply three methods.  And it's passed to this kind of tree.  And it's compared to this kind of thing.  So on the right side one appears frequently.  So please remember this.  It means put three and put three again.  And multiply operation and then give means return.  So it's very simple.  And Ruby VM can understand the right side code as the byte code.  Bite code means the native and VMs language.  So Ruby VM is stack-based instruction.  No, no, no.  Stack-based VM.  So when Ruby VM interprets the byte code, it's rows the instruction one by one.  And it's pushed to stack when the put instruction is read.  And then three is pushed to stack again.  And when it reaches the operation of much, right, it's calculated to nine.  And then it's returned in leave.  From the VM evaluation method returns the nine.  So how is it different?  Is it different from JIT compiler's version?  And with JIT compiler normally it has another thread for JIT compiler.  And from the initial, it interprets like the former way.  It reaches the code count.  Method code count is increased.  JIT compiler detects it as the hotspot.  And JIT compiler compiles the hotspot to native code.  Then VMs switch to just code and native code instead of dispatching instructions by their  self.  So the byte code is the actual code of Ruby.  And after JIT generates the native code from byte code, Ruby VM starts to call native code.  So it's the basic of JIT compiler.  So if the native code is faster than evaluating byte code one by one, it becomes faster.  But why this kind of complex thing should be introduced to Ruby?  So first of all, I want to make things faster.  The reason is one of the reasons is money because fast means that it requires less computer  resources.  So your number of servers can be decreased if the Ruby is optimized.  And also, in this experience, if Ruby becomes two times faster, probably your application  becomes two times faster.  So if the application is faster, probably user experience is improved.  And also, to always use Ruby.  This year, I developed distributed Q middleware with Java because of the parallelism and the  performance.  But I want to use Ruby in ideal situation because I just love Ruby.  So then another criticism is that just in time versus ahead of time, the characteristics  of the JIT compiler is the JIT compiler's program during the execution of the method itself.  And you may think currently, byte code is compiled before the execution, but JIT does  not compile the program before execution.  But the AOT compiles the method to native code before the execution.  So why not AOT for this purpose?  One of the reasons is the keep boot time shorter because the optimizing method to native code  is very time consuming.  And if the compilation is asynchronous, compilation time can be skipped.  So boot time or some one-shot task can be very fast.  And another reason is that some optimization requires the runtime information because Ruby  is very dynamic.  So many things can't be known until it's executed.  So we can use the runtime information for that kind of optimization.  So then another reason is there is working kind of vectoring implementations and that  achieved better performance.  So there are some promising candidates of the JIT compiles.  And the right side one, Mjit is the Vladimir's JIT compiler.  And the one left side that you have, Mjit is one I created.  So they are very faster than Ruby 2.5.  It's the current trunk, most of the trunk.  And the two of the JIT compiles doubles the performance.  So it's worth merging to the trunk maybe.  So I want to explain the mechanisms as much as possible.  There are three implementations I want to introduce here.  Two of them are made by me.  In a half year, I developed two different JIT compiles.  First one, Eril Abbey.  Thank you.  The original title of this talk was the Eril VMS JIT compiler.  But after the VKG finishes, I developed another JIT compiler called FMJIT.  So this talk title is changed.  The middle one, Mjit is developed by Vladimir and introduced by Matt in the first keynote.  So I introduced one of them.  Oh, sorry, three of them.  OK, there.  So Eril Abbey is motivated by the RubyKai 2015 keynote.  It's presented by Evan Phoenix.  It's very impressive.  And I very like the talk.  And its essence was the compiling the Ruby core by CRAN to Eril VMS code and generate  Eril VMS from your byte code.  Then compare them all together.  So if you do that, we can inline Ruby core's methods to Ruby's methods and very optimized.  And I made it possible with Eril Abbey.  Then I introduced how it works.  So when it interprets the byte code frequently, Eril Abbey detects the hotspot by StackProf  like profiler.  And when it's detected as hotspot, JIT compiles the methods to Eril VM.  Not good point is that it's not different thread.  As it's experimental project, it compiles the same thread.  And there is a precompiled Eril VMS code from Ruby's core methods.  So it's the, and the Eril VMS uses both of the Eril VMS.  Then it's optimized with those Eril VMS to native code.  Then switch from a byte code to a native code.  So how is its performance?  Is this well designed for this implementation benchmark?  Was about five times faster.  Why is it fast anyway?  So it is the trick of the Eril VMS optimization is called Eril VMS pass.  Eril VMS pass is a kind of framework to analyze and optimize Eril VMS.  And many of the optimization of CRAN compiler is done in Eril VMS pass.  It's kind of rack and middlewares.  So, and it takes Eril VMS IAL and outputs Eril VMS.  So we can organize many Eril VMS pass you want.  Then probably you can't read them, but they are the Eril VMS pass used in Eril RB.  And affected their performance.  But I could reproduce the same five times faster performance with only two passes.  So let's see how they contribute to performance.  So it would be the secret of Eril RB's optimization.  The summary, just compiling the code to native code is not makes it faster.  Because just comparing to native code means just removing the dispatch of instructions.  Bite code dispatch was the go to, just go to, leaving a pointer of Ravell from some array  and go to some other in function.  Some other in function.  It's not so optimizing.  Then if we add the function inlining pass to this pipeline, it's optimized well.  And Eril CM pass is do invariant code in motion also has the major impact.  But as you can see, inlining function is the very important thing for optimizing native  code.  So I learned that thing from this Eril RB project.  But learnings from Eril RB is that Eril VM is hard to develop with.  And also, Eril RB works with Eril VM 4.0, but it does not work with Eril VM 5.0.  So it's very breaking, yeah.  And also debugging is very important to maintain program for a long time.  But Eril VM is, of course, it's not C language.  So if we, when we do debugging with GDB, we can see C code for native code.  Because it's in Eril VM.  So it will be harder to debug.  So I wanted to tackle another version.  It's built with C language.  So yeah, important learnings from this is that Ruby, main optimization target of Eril  RB is Ruby cores methods.  So directory generating the Eril VM IR was not important or didn't have major impact  for optimization.  So using CRAN would be enough for optimizing C language code of MRI.  So I changed the approach.  Then before I introduce another one, I want to introduce MGIT for the version of Matz  Keynote.  MGIT is developed by Vladimir Makarov.  He's GCC main term.  And MGIT project consists of two components.  It's just not JIT compiler.  It has VM instruction replacement and also MGIT method JIT in compiler.  VM instructions are kind of put object or leave.  All of them were replaced in this project.  And as you were seeing, YARB is currently stack-based VM like Java.  But this project replaces it to register-based language instructions.  And another point I want to notice is this dynamic bytecode specialization.  It also does static bytecode specializations but also dynamic ones.  When method is executed or evaluated in VM, it changes the bytecode to another instruction.  Like this example is INDSET.  INDSET is an RF reference as known set.  And the set has the version of array version or and the argument is integer version.  So many specialized versions are existing in MGIT.  And it's also it will be it will contribute to the JIT performance because if the code  is real small can be smaller.  In the version can be, of course, many optimized real.  So it's good for JIT compiler.  And background, the method difference between error and image is that it's compared in  background thread with pre-thread.  So it's currently not portable to Windows.  But the important thing is that it's compared as synchronously.  So it's good performance.  And it also has the optimization.  You may not know, but during the intermediate states of JIT's calling, it can fall back  to bytecode by restoring the state of original VM states.  So let's see how it works.  Here's original version, current version of MRI.  And it's depressed like this.  The right side's instructions are MGIT's original out-year instructions.  And VM has infinite number of registers.  And MGIT reads the instruction and assigns the values to register instead of stack.  And when during execution, it specializes the instruction by the receiver or arguments  type.  It's good for inlining the definitions in the JIT compiler.  Then register instruction returns the value from register.  It's different from the current behavior.  And MGIT interprets the, during the interprets, bytecode, and the thread compiles the bytecode  to C code.  It's the very kind of invention in MGIT.  It writes the C code to disk directory.  And it's let's GCC or CRAN, the C compiler, binary, or the JIT compiler executes.  X calls exec to execute the compiler.  And it compiles to SO file.  And the DLC function allows us to roll the native code as C function pointer.  So we can call that C function from VM instead of evaluating the bytecode.  So how is MGIT?  MGIT achieved great performance.  Very great.  It claims three times faster from Ruby 2.4.  It was not true in my machine, but kind of very fast.  And replacing, but also it replaces the compiler to generate another instruction sets.  And also VM instruction.  So replacing VM instruction at all might be very risky.  So I thought I want to fix this.  And I focused this.  Yeah, MGIT.  Yeah.  This is the focus of MGIT project.  And this project is currently I'm working on.  And yeah.  And the major characteristic is that it achieves JIT comparison without changing VM  instructions at all.  So it simulates stack on comparison and does not require register-based instructions.  So let's see how MGIT optimizes this.  Okay.  I understood.  So before seeing how JIT compiler optimizes the method, how would you optimize this method  in VM level?  You may think this can be possible.  Like just putting nine from initial.  But consider this method.  This is the definition allows us to do this kind of three, multiply three equals to three  somehow.  So this optimization can't be done in not just impossible, but it's hard.  And currently YARB does not optimize in this way.  But in YARB MGIT, during its interpretation, JIT compiler checks the core count and generates  C code in like this.  Probably it's hard to see, but just doing automates is called with three arguments.  And also, important thing is that it has the definition of automates.  It is for inline in the future.  Because I learned inlining is important in LRB.  I made sure that it's inline in this version.  So C compiler compiles this source code.  And then if the method definition of automates is included in this source code, it's inline  in this way in C compiler.  And then as three and three is provided in this source code, the three multiplied three  is calculated beforehand before execution.  And also, notable thing is that it checks the method definition properly.  So it really works.  And we can return the result.  Optimized result, it's optimized by C compiler.  I want to say that this is not optimized by our work, but it's optimized by other C  compiler's efforts.  So optimizing code would be very complex thing, but we can delegate such hard work to see  very good other things.  Then it's loaded as, so file and the DLC into native code and call this one.  So we can return the optimized nine, just nine, without calculating the multiply.  Of course, it's, we probably, such optimization wouldn't be needed just if just it's a multiply  calculation.  But another very complex version of the optimization can be provided because we don't do some specific  optimization in ourselves.  So basic ideas to optimize the MGAID is the inlining functions and skip unnecessary work  and decrease in comparison time.  And so I introduce secrets of MGAID's optimizations.  Many of the following sites are in only text.  So you may be sleepy, but please ask me questions later.  The first one is inlining function by having VM source code as header.  MGAID originally do this by compiling VM.c beforehand and compiling not fully compiled,  but just preprocessing header include or define or things like that.  So by having those VM instructions, many of the frequently used methods in VM instructions,  we can inline those methods.  And then transform functions in header static.  As the VM.c compiler header can be dealt with by our script, we can modify this header by  our Ruby script.  And we as the static to the non-static functions.  And if the function is static, c compiler can know it is not necessary to be compiled.  So comparison is skipped for those static functions.  And another technique is skip set jump.  You may not know set jump, but it is used for implementing exception.  Exception is implemented with set jump and jump.  And of course, it needs to store some states of the VM, so it is very slow.  So it skips it.  If the method does not seem to raise exception.  So of course, we need to check it may not raise exception, but currently YARB-MJIT does  not check it properly.  So I will fix it right here.  And about inline ingarage function, it is for just decreasing comparison time.  When I developed YARB-MJIT in the first, its comparison is very slow, like 500 milliseconds  for each method.  But if it takes 500 milliseconds for one method comparison, very limited number of methods  can be compiled during the benchmark.  So Octogal score was very small in that state.  So VM search method is the function that searches the method.  So it is very complex things, because Ruby is very complex about Ruby methods.  So it is very slow to compile.  So YARB-MJIT also implements the optimization to skip that execution.  And then very complicated thing is the base pointer.  I can't explain this precisely.  Because the JIT code is executed frequently, only JIT compiler compiles hotspots.  So all of the methods compiled one is the hotspot.  So all of the very small tasks would have impact.  And we can calculate the base pointer is needed to restore the state before the JIT  run.  Please ask this later.  And the last two ones are inline external functions using VM sessions.  Some of the functions in instances.dev is not static run.  And it's linked as the external functions.  So as the JIT header is compiled from VM.c, functions not in VM.c are not available for  VM.jit compiler.  So I added some of the methods definitions to header by some efforts.  But Mjit does this by dynamic bytecode specialization.  Because it replaces the instructions to some specialised ones, specialised ones has the  definitions.  So Mjit can inline those things.  So it's faster.  And the last one is the inline set of methods code.  This is using Lantain information.  And Ruby VM has the call cache in bytecode.  bytecode has its inline cache.  Call cache is used to implement inline method cache.  Inline method cache is the only use for the one place.  Sorry.  It's very hard to explain.  But it has the definition of the method.  And the cache is created during the execution.  So we can use the call cache to detect which type is used for Ruby method.  So there are many types of Ruby methods like implemented in Ruby and implemented in C function  or ADS or attribute reader or things like that.  If we detect those types, we can inline the set of methods code.  And this optimization had a large impact.  Then with those techniques, YARB Mjit achieved this very good performance compared to current  trunk.  So future works of YARB Mjit optimizations are both more techniques from Mjit because  it's folk.  Removing unnecessary functions in per combined header is not implemented yet because it depends  on some RTA code.  Also I want to, I think I can inline core methods defining C function because we can  detect the type in call cache.  And we, probably we can know the which function can be used for the Ruby C function defined  method code.  And probably we can inline a method defined with byte code by using the detecting it by  call cache.  So no, no, no, no, no.  It needs to save some last compiled some gt code.  If we store some gtc functions, previous functions, we can use it to refer to the method.  So I think method defined with byte code can be inline.  The main part of this talk is how can we realize JIT for MRI because this is conference.  So many people have failed to introduce JIT.  There were some talks about JIT compiler in RubyKaigi.  So LRV is very similar or uses that same technique with YARB to LRVM.  It's very old project and we have failed from such old age.  Also there was promising JIT compiler, tracing based JIT compiler, but also it's not merged  to Ruby core for now.  And also Ruby news, probably you may know Ruby news uses LRVM and it has JIT compiler.  I studied, I read the previous code before I implement LRV, but actually it didn't have  JIT compiler in 3.0 because they said JIT compiler is hard to maintain and it has many bugs.  So it's very difficult thing to introduce JIT.  But we should solve those problems.  Why did they fail?  YARB to LRVM was how to improve performance with MRI time because probably it's very old.  So the VM are different.  And Ruby was memory consuming so it's not merged.  But MZT solved this part well.  Also Ruby news was how to fix bugs in JIT.  So let's see one by one those problems.  Can we solve improved performance?  So MZT achieved the performance and I did improved performance.  And also many of you good people, Endosand and Noa Gibbs made very good benchmarks.  So thank you.  And so it's very good for improving performance.  And memory consuming MZT solved this problem too and how to fix bugs is very large problem.  So MZT does not use LRVM directory.  It can be used by a crank but it doesn't use LRVM directory.  And so no need to keep up with breaking changes.  And we can debug using C code using GDB.  So very easy.  Very so easier to debug in C code.  Because as VM code is written in C, we can debug is only seeing C codes.  But it needs many changes for interaction.  And the very normal thing is the rest you change, the more unlikely it will be hub bugs.  So I want to reduce the changes.  And I want to say in this talk make initial release as safe as possible.  Because we are using Ruby in production.  And also my application in my company is very serious to want to be, it's very, I want to  make it to be very stable.  So I don't want to introduce very breaking change to Ruby.  So don't solve many difficult problems at the same time.  So MVGIT does replacing VM instructions, all of the instructions, and introduce this compiler,  but it's not portable.  So we need to do two different very difficult problems.  And I want to make it optional at first.  But we can't turn off replacing VM instructions because byte code is compiled beforehand.  And unlike MVGIT, it can't be turned off.  So if the only MVGIT is introduced, we can turn off the JIT compiler by just stop to  pass the option.  So even if we found the problem in JIT compiler, we can turn off.  So it's very safe.  And also I want to change it gradually.  So big one release is very risky.  And it's hard to develop.  It's also hard to develop.  Because releasing may face conflicts, many conflicts.  I want to develop in gradually.  So this is the current status of those JIT projects.  MVGIT is not slower than Ruby 2.0, but it's actually slower than current Ruby trunk.  But YARV-MGIT does not modify VM, so it's not slower.  And another thing is that YARV-MGIT does not modify VM instructions.  So it passes all the tests in Ruby core without JIT.  But MVGIT replaces instructions, and it fails with current tests and Ruby spec.  So I think YARV-MGIT is very safer.  So we need your help.  Please try and report bugs to those repositories.  Reporting bugs is very helpful to develop.  And one of the Ruby committers is reporting bugs to YARV-MGIT and it's helpful.  So I write this, how to use YARV-MGIT, so read this.  I published this.  Conclusion.  Methods compare for MRA can gain good performance by inlining.  And YARV-MGIT is a safe migration path to realize realization.  I want to realize JIT compiler.  To realize it, bug reports using Minerary are very helpful.  That's all.  Thank you.  Thank you.
LLVM-based_JIT_compiler_for_MRI_0_47.png LLVM-based_JIT_compiler_for_MRI_48_131.png LLVM-based_JIT_compiler_for_MRI_132_287.png LLVM-based_JIT_compiler_for_MRI_288_539.png LLVM-based_JIT_compiler_for_MRI_540_623.png LLVM-based_JIT_compiler_for_MRI_624_1079.png LLVM-based_JIT_compiler_for_MRI_1080_1343.png LLVM-based_JIT_compiler_for_MRI_1344_2087.png LLVM-based_JIT_compiler_for_MRI_2088_2543.png LLVM-based_JIT_compiler_for_MRI_2544_2783.png LLVM-based_JIT_compiler_for_MRI_2784_2915.png LLVM-based_JIT_compiler_for_MRI_2916_3191.png LLVM-based_JIT_compiler_for_MRI_3192_3323.png LLVM-based_JIT_compiler_for_MRI_3324_3479.png LLVM-based_JIT_compiler_for_MRI_3480_3575.png LLVM-based_JIT_compiler_for_MRI_3576_3887.png LLVM-based_JIT_compiler_for_MRI_3888_4151.png LLVM-based_JIT_compiler_for_MRI_4152_4643.png LLVM-based_JIT_compiler_for_MRI_4644_4907.png LLVM-based_JIT_compiler_for_MRI_4908_5159.png LLVM-based_JIT_compiler_for_MRI_5160_9023.png LLVM-based_JIT_compiler_for_MRI_9024_9179.png LLVM-based_JIT_compiler_for_MRI_9180_10739.png LLVM-based_JIT_compiler_for_MRI_10740_12803.png LLVM-based_JIT_compiler_for_MRI_12804_14123.png LLVM-based_JIT_compiler_for_MRI_14124_14315.png LLVM-based_JIT_compiler_for_MRI_14316_16007.png LLVM-based_JIT_compiler_for_MRI_16008_16835.png LLVM-based_JIT_compiler_for_MRI_16836_18575.png LLVM-based_JIT_compiler_for_MRI_18576_18623.png LLVM-based_JIT_compiler_for_MRI_18624_18767.png LLVM-based_JIT_compiler_for_MRI_18768_18899.png LLVM-based_JIT_compiler_for_MRI_18900_18947.png LLVM-based_JIT_compiler_for_MRI_18948_19559.png LLVM-based_JIT_compiler_for_MRI_19560_22271.png LLVM-based_JIT_compiler_for_MRI_22272_24539.png LLVM-based_JIT_compiler_for_MRI_24540_25223.png LLVM-based_JIT_compiler_for_MRI_25224_28631.png LLVM-based_JIT_compiler_for_MRI_28632_33107.png LLVM-based_JIT_compiler_for_MRI_33108_37247.png LLVM-based_JIT_compiler_for_MRI_37248_38327.png LLVM-based_JIT_compiler_for_MRI_38328_38759.png LLVM-based_JIT_compiler_for_MRI_38760_39035.png LLVM-based_JIT_compiler_for_MRI_39036_43295.png LLVM-based_JIT_compiler_for_MRI_43296_44387.png LLVM-based_JIT_compiler_for_MRI_44388_47783.png LLVM-based_JIT_compiler_for_MRI_47784_48023.png LLVM-based_JIT_compiler_for_MRI_48024_50111.png LLVM-based_JIT_compiler_for_MRI_50112_51959.png LLVM-based_JIT_compiler_for_MRI_51960_53267.png LLVM-based_JIT_compiler_for_MRI_53268_53339.png LLVM-based_JIT_compiler_for_MRI_53340_54827.png LLVM-based_JIT_compiler_for_MRI_54828_57383.png LLVM-based_JIT_compiler_for_MRI_57384_58991.png LLVM-based_JIT_compiler_for_MRI_58992_59111.png LLVM-based_JIT_compiler_for_MRI_59112_59183.png LLVM-based_JIT_compiler_for_MRI_59184_59819.png LLVM-based_JIT_compiler_for_MRI_59820_59975.png LLVM-based_JIT_compiler_for_MRI_59976_60008.png
Today I'll talk about just-in-time compiler, whole MRI or CRB. And I'm Takashi Kokubin, so let me introduce myself first. Last year of the week, there are some similar people from Japanese. People are confused, who is Matt or Koichi or me. They are wearing grasses or black foodies. And so this year I'm wearing very convenient t-shirts that has icons. So, yeah, nice to meet you. This is my icon, so please remember me with this. Even if I attended last year and I met some people, and this year some people said to me, nice to meet you, but some of you guys I met before. And so one of them, my project is the Hamlet. This is the original 8x faster version of Hamlet. So I'm interested in template engine optimization. Then I joined Hamlet organization. And this year I optimized Hamlet 4x faster. Yeah. And then joined the Ruby organization and ERB maintainer. Then I made ERB 2x faster. This will be shifting to 2.5. So I love fast things and making things faster. And I love Ruby language. So why not I optimized Ruby? So it's time to introduce Jet Compire. I did this. The right one is the Jet Compire I made this year. So this talk focuses on how I achieve this. Before that, probably many of you have no experience to develop Jet Compire. So I introduced what's Jet Compire. Jet is the abbreviation of just in time. And Jet Compire optimizes the program by comparing the program to native code. But probably many of you guys have no experience to compile to native code. So I will show the difference between native code and byte code. And so example method is that this is just three, multiply three methods. And it's passed to this kind of tree. And it's compared to this kind of thing. So on the right side one appears frequently. So please remember this. It means put three and put three again. And multiply operation and then give means return. So it's very simple. And Ruby VM can understand the right side code as the byte code. Bite code means the native and VMs language. So Ruby VM is stack-based instruction. No, no, no. Stack-based VM. So when Ruby VM interprets the byte code, it's rows the instruction one by one. And it's pushed to stack when the put instruction is read. And then three is pushed to stack again. And when it reaches the operation of much, right, it's calculated to nine. And then it's returned in leave. From the VM evaluation method returns the nine. So how is it different? Is it different from JIT compiler's version? And with JIT compiler normally it has another thread for JIT compiler. And from the initial, it interprets like the former way. It reaches the code count. Method code count is increased. JIT compiler detects it as the hotspot. And JIT compiler compiles the hotspot to native code. Then VMs switch to just code and native code instead of dispatching instructions by their self. So the byte code is the actual code of Ruby. And after JIT generates the native code from byte code, Ruby VM starts to call native code. So it's the basic of JIT compiler. So if the native code is faster than evaluating byte code one by one, it becomes faster. But why this kind of complex thing should be introduced to Ruby? So first of all, I want to make things faster. The reason is one of the reasons is money because fast means that it requires less computer resources. So your number of servers can be decreased if the Ruby is optimized. And also, in this experience, if Ruby becomes two times faster, probably your application becomes two times faster. So if the application is faster, probably user experience is improved. And also, to always use Ruby. This year, I developed distributed Q middleware with Java because of the parallelism and the performance. But I want to use Ruby in ideal situation because I just love Ruby. So then another criticism is that just in time versus ahead of time, the characteristics of the JIT compiler is the JIT compiler's program during the execution of the method itself. And you may think currently, byte code is compiled before the execution, but JIT does not compile the program before execution. But the AOT compiles the method to native code before the execution. So why not AOT for this purpose? One of the reasons is the keep boot time shorter because the optimizing method to native code is very time consuming. And if the compilation is asynchronous, compilation time can be skipped. So boot time or some one-shot task can be very fast. And another reason is that some optimization requires the runtime information because Ruby is very dynamic. So many things can't be known until it's executed. So we can use the runtime information for that kind of optimization. So then another reason is there is working kind of vectoring implementations and that achieved better performance. So there are some promising candidates of the JIT compiles. And the right side one, Mjit is the Vladimir's JIT compiler. And the one left side that you have, Mjit is one I created. So they are very faster than Ruby 2.5. It's the current trunk, most of the trunk. And the two of the JIT compiles doubles the performance. So it's worth merging to the trunk maybe. So I want to explain the mechanisms as much as possible. There are three implementations I want to introduce here. Two of them are made by me. In a half year, I developed two different JIT compiles. First one, Eril Abbey. Thank you. The original title of this talk was the Eril VMS JIT compiler. But after the VKG finishes, I developed another JIT compiler called FMJIT. So this talk title is changed. The middle one, Mjit is developed by Vladimir and introduced by Matt in the first keynote. So I introduced one of them. Oh, sorry, three of them. OK, there. So Eril Abbey is motivated by the RubyKai 2015 keynote. It's presented by Evan Phoenix. It's very impressive. And I very like the talk. And its essence was the compiling the Ruby core by CRAN to Eril VMS code and generate Eril VMS from your byte code. Then compare them all together. So if you do that, we can inline Ruby core's methods to Ruby's methods and very optimized. And I made it possible with Eril Abbey. Then I introduced how it works. So when it interprets the byte code frequently, Eril Abbey detects the hotspot by StackProf like profiler. And when it's detected as hotspot, JIT compiles the methods to Eril VM. Not good point is that it's not different thread. As it's experimental project, it compiles the same thread. And there is a precompiled Eril VMS code from Ruby's core methods. So it's the, and the Eril VMS uses both of the Eril VMS. Then it's optimized with those Eril VMS to native code. Then switch from a byte code to a native code. So how is its performance? Is this well designed for this implementation benchmark? Was about five times faster. Why is it fast anyway? So it is the trick of the Eril VMS optimization is called Eril VMS pass. Eril VMS pass is a kind of framework to analyze and optimize Eril VMS. And many of the optimization of CRAN compiler is done in Eril VMS pass. It's kind of rack and middlewares. So, and it takes Eril VMS IAL and outputs Eril VMS. So we can organize many Eril VMS pass you want. Then probably you can't read them, but they are the Eril VMS pass used in Eril RB. And affected their performance. But I could reproduce the same five times faster performance with only two passes. So let's see how they contribute to performance. So it would be the secret of Eril RB's optimization. The summary, just compiling the code to native code is not makes it faster. Because just comparing to native code means just removing the dispatch of instructions. Bite code dispatch was the go to, just go to, leaving a pointer of Ravell from some array and go to some other in function. Some other in function. It's not so optimizing. Then if we add the function inlining pass to this pipeline, it's optimized well. And Eril CM pass is do invariant code in motion also has the major impact. But as you can see, inlining function is the very important thing for optimizing native code. So I learned that thing from this Eril RB project. But learnings from Eril RB is that Eril VM is hard to develop with. And also, Eril RB works with Eril VM 4.0, but it does not work with Eril VM 5.0. So it's very breaking, yeah. And also debugging is very important to maintain program for a long time. But Eril VM is, of course, it's not C language. So if we, when we do debugging with GDB, we can see C code for native code. Because it's in Eril VM. So it will be harder to debug. So I wanted to tackle another version. It's built with C language. So yeah, important learnings from this is that Ruby, main optimization target of Eril RB is Ruby cores methods. So directory generating the Eril VM IR was not important or didn't have major impact for optimization. So using CRAN would be enough for optimizing C language code of MRI. So I changed the approach. Then before I introduce another one, I want to introduce MGIT for the version of Matz Keynote. MGIT is developed by Vladimir Makarov. He's GCC main term. And MGIT project consists of two components. It's just not JIT compiler. It has VM instruction replacement and also MGIT method JIT in compiler. VM instructions are kind of put object or leave. All of them were replaced in this project. And as you were seeing, YARB is currently stack-based VM like Java. But this project replaces it to register-based language instructions. And another point I want to notice is this dynamic bytecode specialization. It also does static bytecode specializations but also dynamic ones. When method is executed or evaluated in VM, it changes the bytecode to another instruction. Like this example is INDSET. INDSET is an RF reference as known set. And the set has the version of array version or and the argument is integer version. So many specialized versions are existing in MGIT. And it's also it will be it will contribute to the JIT performance because if the code is real small can be smaller. In the version can be, of course, many optimized real. So it's good for JIT compiler. And background, the method difference between error and image is that it's compared in background thread with pre-thread. So it's currently not portable to Windows. But the important thing is that it's compared as synchronously. So it's good performance. And it also has the optimization. You may not know, but during the intermediate states of JIT's calling, it can fall back to bytecode by restoring the state of original VM states. So let's see how it works. Here's original version, current version of MRI. And it's depressed like this. The right side's instructions are MGIT's original out-year instructions. And VM has infinite number of registers. And MGIT reads the instruction and assigns the values to register instead of stack. And when during execution, it specializes the instruction by the receiver or arguments type. It's good for inlining the definitions in the JIT compiler. Then register instruction returns the value from register. It's different from the current behavior. And MGIT interprets the, during the interprets, bytecode, and the thread compiles the bytecode to C code. It's the very kind of invention in MGIT. It writes the C code to disk directory. And it's let's GCC or CRAN, the C compiler, binary, or the JIT compiler executes. X calls exec to execute the compiler. And it compiles to SO file. And the DLC function allows us to roll the native code as C function pointer. So we can call that C function from VM instead of evaluating the bytecode. So how is MGIT? MGIT achieved great performance. Very great. It claims three times faster from Ruby 2.4. It was not true in my machine, but kind of very fast. And replacing, but also it replaces the compiler to generate another instruction sets. And also VM instruction. So replacing VM instruction at all might be very risky. So I thought I want to fix this. And I focused this. Yeah, MGIT. Yeah. This is the focus of MGIT project. And this project is currently I'm working on. And yeah. And the major characteristic is that it achieves JIT comparison without changing VM instructions at all. So it simulates stack on comparison and does not require register-based instructions. So let's see how MGIT optimizes this. Okay. I understood. So before seeing how JIT compiler optimizes the method, how would you optimize this method in VM level? You may think this can be possible. Like just putting nine from initial. But consider this method. This is the definition allows us to do this kind of three, multiply three equals to three somehow. So this optimization can't be done in not just impossible, but it's hard. And currently YARB does not optimize in this way. But in YARB MGIT, during its interpretation, JIT compiler checks the core count and generates C code in like this. Probably it's hard to see, but just doing automates is called with three arguments. And also, important thing is that it has the definition of automates. It is for inline in the future. Because I learned inlining is important in LRB. I made sure that it's inline in this version. So C compiler compiles this source code. And then if the method definition of automates is included in this source code, it's inline in this way in C compiler. And then as three and three is provided in this source code, the three multiplied three is calculated beforehand before execution. And also, notable thing is that it checks the method definition properly. So it really works. And we can return the result. Optimized result, it's optimized by C compiler. I want to say that this is not optimized by our work, but it's optimized by other C compiler's efforts. So optimizing code would be very complex thing, but we can delegate such hard work to see very good other things. Then it's loaded as, so file and the DLC into native code and call this one. So we can return the optimized nine, just nine, without calculating the multiply. Of course, it's, we probably, such optimization wouldn't be needed just if just it's a multiply calculation. But another very complex version of the optimization can be provided because we don't do some specific optimization in ourselves. So basic ideas to optimize the MGAID is the inlining functions and skip unnecessary work and decrease in comparison time. And so I introduce secrets of MGAID's optimizations. Many of the following sites are in only text. So you may be sleepy, but please ask me questions later. The first one is inlining function by having VM source code as header. MGAID originally do this by compiling VM.c beforehand and compiling not fully compiled, but just preprocessing header include or define or things like that. So by having those VM instructions, many of the frequently used methods in VM instructions, we can inline those methods. And then transform functions in header static. As the VM.c compiler header can be dealt with by our script, we can modify this header by our Ruby script. And we as the static to the non-static functions. And if the function is static, c compiler can know it is not necessary to be compiled. So comparison is skipped for those static functions. And another technique is skip set jump. You may not know set jump, but it is used for implementing exception. Exception is implemented with set jump and jump. And of course, it needs to store some states of the VM, so it is very slow. So it skips it. If the method does not seem to raise exception. So of course, we need to check it may not raise exception, but currently YARB-MJIT does not check it properly. So I will fix it right here. And about inline ingarage function, it is for just decreasing comparison time. When I developed YARB-MJIT in the first, its comparison is very slow, like 500 milliseconds for each method. But if it takes 500 milliseconds for one method comparison, very limited number of methods can be compiled during the benchmark. So Octogal score was very small in that state. So VM search method is the function that searches the method. So it is very complex things, because Ruby is very complex about Ruby methods. So it is very slow to compile. So YARB-MJIT also implements the optimization to skip that execution. And then very complicated thing is the base pointer. I can't explain this precisely. Because the JIT code is executed frequently, only JIT compiler compiles hotspots. So all of the methods compiled one is the hotspot. So all of the very small tasks would have impact. And we can calculate the base pointer is needed to restore the state before the JIT run. Please ask this later. And the last two ones are inline external functions using VM sessions. Some of the functions in instances.dev is not static run. And it's linked as the external functions. So as the JIT header is compiled from VM.c, functions not in VM.c are not available for VM.jit compiler. So I added some of the methods definitions to header by some efforts. But Mjit does this by dynamic bytecode specialization. Because it replaces the instructions to some specialised ones, specialised ones has the definitions. So Mjit can inline those things. So it's faster. And the last one is the inline set of methods code. This is using Lantain information. And Ruby VM has the call cache in bytecode. bytecode has its inline cache. Call cache is used to implement inline method cache. Inline method cache is the only use for the one place. Sorry. It's very hard to explain. But it has the definition of the method. And the cache is created during the execution. So we can use the call cache to detect which type is used for Ruby method. So there are many types of Ruby methods like implemented in Ruby and implemented in C function or ADS or attribute reader or things like that. If we detect those types, we can inline the set of methods code. And this optimization had a large impact. Then with those techniques, YARB Mjit achieved this very good performance compared to current trunk. So future works of YARB Mjit optimizations are both more techniques from Mjit because it's folk. Removing unnecessary functions in per combined header is not implemented yet because it depends on some RTA code. Also I want to, I think I can inline core methods defining C function because we can detect the type in call cache. And we, probably we can know the which function can be used for the Ruby C function defined method code. And probably we can inline a method defined with byte code by using the detecting it by call cache. So no, no, no, no, no. It needs to save some last compiled some gt code. If we store some gtc functions, previous functions, we can use it to refer to the method. So I think method defined with byte code can be inline. The main part of this talk is how can we realize JIT for MRI because this is conference. So many people have failed to introduce JIT. There were some talks about JIT compiler in RubyKaigi. So LRV is very similar or uses that same technique with YARB to LRVM. It's very old project and we have failed from such old age. Also there was promising JIT compiler, tracing based JIT compiler, but also it's not merged to Ruby core for now. And also Ruby news, probably you may know Ruby news uses LRVM and it has JIT compiler. I studied, I read the previous code before I implement LRV, but actually it didn't have JIT compiler in 3.0 because they said JIT compiler is hard to maintain and it has many bugs. So it's very difficult thing to introduce JIT. But we should solve those problems. Why did they fail? YARB to LRVM was how to improve performance with MRI time because probably it's very old. So the VM are different. And Ruby was memory consuming so it's not merged. But MZT solved this part well. Also Ruby news was how to fix bugs in JIT. So let's see one by one those problems. Can we solve improved performance? So MZT achieved the performance and I did improved performance. And also many of you good people, Endosand and Noa Gibbs made very good benchmarks. So thank you. And so it's very good for improving performance. And memory consuming MZT solved this problem too and how to fix bugs is very large problem. So MZT does not use LRVM directory. It can be used by a crank but it doesn't use LRVM directory. And so no need to keep up with breaking changes. And we can debug using C code using GDB. So very easy. Very so easier to debug in C code. Because as VM code is written in C, we can debug is only seeing C codes. But it needs many changes for interaction. And the very normal thing is the rest you change, the more unlikely it will be hub bugs. So I want to reduce the changes. And I want to say in this talk make initial release as safe as possible. Because we are using Ruby in production. And also my application in my company is very serious to want to be, it's very, I want to make it to be very stable. So I don't want to introduce very breaking change to Ruby. So don't solve many difficult problems at the same time. So MVGIT does replacing VM instructions, all of the instructions, and introduce this compiler, but it's not portable. So we need to do two different very difficult problems. And I want to make it optional at first. But we can't turn off replacing VM instructions because byte code is compiled beforehand. And unlike MVGIT, it can't be turned off. So if the only MVGIT is introduced, we can turn off the JIT compiler by just stop to pass the option. So even if we found the problem in JIT compiler, we can turn off. So it's very safe. And also I want to change it gradually. So big one release is very risky. And it's hard to develop. It's also hard to develop. Because releasing may face conflicts, many conflicts. I want to develop in gradually. So this is the current status of those JIT projects. MVGIT is not slower than Ruby 2.0, but it's actually slower than current Ruby trunk. But YARV-MGIT does not modify VM, so it's not slower. And another thing is that YARV-MGIT does not modify VM instructions. So it passes all the tests in Ruby core without JIT. But MVGIT replaces instructions, and it fails with current tests and Ruby spec. So I think YARV-MGIT is very safer. So we need your help. Please try and report bugs to those repositories. Reporting bugs is very helpful to develop. And one of the Ruby committers is reporting bugs to YARV-MGIT and it's helpful. So I write this, how to use YARV-MGIT, so read this. I published this. Conclusion. Methods compare for MRA can gain good performance by inlining. And YARV-MGIT is a safe migration path to realize realization. I want to realize JIT compiler. To realize it, bug reports using Minerary are very helpful. That's all. Thank you. Thank you.
this year. So this talk focuses on how I achieve will show the difference between native code and performance. But I want to use Ruby in ideal times faster. Why is it fast anyway? So it is the code is not makes it faster. Because just before seeing how JIT compiler optimizes the compiler. I want to say that this is not compiler can know it is not necessary to be limited number of methods can be compiled during compiled from VM.c, functions not in VM.c are not it's helpful. So I write this, how to use