How to use Ruby's JIT compiler¶
Project status¶
Experimental.
We fixed most of known bugs and portability issues on Windows.
While we achieved 1.7x performance improvement in CPU-intensive benchmark https://github.com/mame/optcarrot,
this still may NOT improve performance for real-world workloads like web applications.
Basic usage¶
--jit¶
This feature is disabled by default since it's still experimental. When you execute ruby command, you need to pass --jit flag.
$ ruby --jit script.rb
$RUBYOPT¶
When you're not executing ruby command to run your application, you can pass any JIT-related flag on $RUBYOPT.
$ RUBYOPT="--jit" bin/rails s
Debugging¶
If you want to see/debug what's happening behind it, use "--jit-verbose=1" or "--jit-verbose=2".
If you use "--jit-save-temps", you can check temporary files which are deleted during one execution.
$ ruby --disable-gems --jit-verbose=1 --jit-save-temps -e "def a; nil; end; a;a;a;a;a; RubyVM::MJIT.pause"
JIT success (19.3ms): a@-e:1 -> /tmp/_ruby_mjit_p8160u0.c
Successful MJIT finish
$ head -n11 /tmp/_ruby_mjit_p8160u0.c
#include "/tmp/_ruby_mjit_hp8160u0.h"
/* a@-e:1 */
#undef OPT_CHECKED_RUN
#define OPT_CHECKED_RUN 0
VALUE
_mjit0(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp)
{
VALUE stack[1];
static const VALUE *const original_body_iseq = (VALUE *)0x55d1ababf530;
Note that a method is queued to be JIT-ed when it's called 5 times.
RubyVM::MJIT.pause
is a method to wait for JIT compilations and then shutdown a thread to run C compilers for JIT (call RubyVM::MJIT.pause
to restart the thread).
See "ruby --help" to know other options.
Performance characteristics¶
Advantages¶
- Overhead of Virtual Machine interpretation is always eliminated by JIT's very nature
- Basic instructions (+, -, *, /, [], ...) on core classes (Integer, String, Array, Hash, ...) are optimized more
- Ruby method call is optimized using method cache information, especially when JIT-ed method is called by JIT-ed method
- Instance variable access is optimized using inline cache information
Disadvantages¶
- Overhead of loading JIT-ed code is always added.
- This overhead could be bigger than performance gain by JIT, especially on small methods.
- The overhead is increased especially when there are many methods to be JIT-ed.
- C compiler processes can steal CPU / memory resources while JIT compilation is still happening.
- Calling non-JIT-ed Ruby method from JIT-ed method has overhead to invoke new Virtual Machine.
MJIT organization¶
The current architecture of Ruby's JIT is called "MJIT", which is proposed by Vladimir Makarov [Feature #12589].
Then it's merged as a more conservative form in [Feature #14235].
So it's slightly different from the original design in: https://github.com/vnmakarov/ruby/tree/rtl_mjit_branch#mjit-organization
_______ _________________
|header |-->| minimized header|
|_______| |_________________|
| MRI building
--------------|----------------------------------------
| MRI execution
|
_____________|_____
| | |
| ___V__ | CC ____________________
| | |----------->| precompiled header |
| | | | |____________________|
| | | | |
| | MJIT | | |
| | | | |
| | | | ____V___ CC __________
| |______|----------->| C code |--->| .so file |
| | |________| |__________|
| | |
| | |
| MRI machine code |<-----------------------------
|___________________| loading
-
MJIT is a method JIT (one more reason for the name)
-
An important organization goal is to minimize the JIT compilation time
-
To simplify JIT implementation the environment (C code header needed
to C code generated by MJIT) is justvm.c
file -
A special Ruby script minimize the environment
* Removing about 90% declarations -
MJIT has a thread to do parallel compilation
-
It prepares a precompiled code of the minimized header
- It starts at the MRI execution start
-
It generates PIC object files of ISEQs
- They start when the precompiled header is ready
- They take ISEQs from a priority queue unless it is empty.
- They translate ISEQs into C-code using the precompiled header,
call CC and load PIC code when it is ready
-
-
MJIT put ISEQ in the queue when ISEQ is called
-
MJIT can reorder ISEQs in the queue if some ISEQ has been called many
times and its compilation did not start yet or we need the ISEQ code
for AOT -
MRI reuses the machine code if it already exists for ISEQ
-
All files are stored in
/tmp
. On modern Linux/tmp
is a file
system in memory -
The machine code execution can stop and switch to the ISEQ
interpretation if some condition is not satisfied as the machine
code can be speculative or some exception raises -
When TracePoint is enabled, all currently executed JIT functions are
canceled and the corresponding ISEQs continue their execution in the interpreter mode -
MJIT options can be given on the command line or by environment
variable RUBYOPT
Related resources¶