Slide 1

Slide 1 text

Optimizing Production Performance with MRI JIT @k0kubun / Takashi Kokubun

Slide 2

Slide 2 text

Self introduction ● GitHub, Twitter: @k0kubun ● Ruby committer: JIT, ERB, IRB ● Company: Treasure Data

Slide 3

Slide 3 text

Agenda ● Introduction to MRI JIT ● Tuning JIT performance for Rails ● Warming up MRI JIT ● The future of MRI JIT

Slide 4

Slide 4 text

Introduction to MRI JIT

Slide 5

Slide 5 text

The list of MRI JITs ● MJIT ● YJIT ● MIR

Slide 6

Slide 6 text

MJIT ● Merged in Ruby 2.6 ● Optionally enabled by --jit ● Run a C compiler at runtime ● Support GCC, Clang, and MSVC

Slide 7

Slide 7 text

YJIT ● To be merged in Ruby 3.1 ● Optionally enabled by --yjit ● In-process x86 assembler

Slide 8

Slide 8 text

MIR ● JIT framework, motivated by MJIT ● Planned to be integrated with MRI ● Inline C functions without a C compiler

Slide 9

Slide 9 text

Layers of JIT implementation VM JIT compiler Codegen RTL YARV RTL-MJIT MJIT (C compiler) YARV-MJIT (mjit_compile.c) MIR YJIT yjit_codegen.c MIR-based JIT Ruby 2.6~3.0 Feature #12589 MIR YJIT

Slide 10

Slide 10 text

Today's theme ● How to make your application faster in Ruby 3.0 ○ i.e. with MJIT

Slide 11

Slide 11 text

Tuning JIT performance for Rails

Slide 12

Slide 12 text

If you don't tune MJIT https://speed.yjit.org

Slide 13

Slide 13 text

Tuned peak performance https://gist.github.com/k0kubun/cbc5251be1c19e36b7b7f786db302465

Slide 14

Slide 14 text

Key ideas ● Some versions are slow ● TracePoint, GC.compact, and Ractor ● Change --jit-max-cache for Ruby 3.0 ● Wait until everything is compiled

Slide 15

Slide 15 text

Some versions are slow ● Don't use Ruby 2.x ○ Ruby 3.0 has better CPU cache efficiency ● Even Ruby 3 has slow versions ○ MJIT doesn't work properly in Ruby 3.0.1 ○ Ruby 3.0.0 is OK, but others might have throttling issues

Slide 16

Slide 16 text

TracePoint, GC.compact, and Ractor ● MJIT can be disabled when GC.compact or TracePoint is used ○ Ruby 3.1 shows "JIT cancel" on --jit-verbose=1 when it happens ● However, Ruby 3.1 supported TracePoint :class events for Zeitwerk ● MJIT has performance issues when you have Ractors

Slide 17

Slide 17 text

Change --jit-max-cache for Ruby 3.0 ● The default --jit-max-cache is 100 in Ruby 3.0 ● It should be large enough to compile everything, like 10,000 ○ Use --jit-verbose=1 to see what's happening

Slide 18

Slide 18 text

Wait until everything is compiled ● When a C compiler is running, the interpreter becomes slower ○ We've found no workaround so far ● So be sure to see the end of compilation with --jit-verbose=1 ○ This can take some minutes

Slide 19

Slide 19 text

Warming up MRI JIT

Slide 20

Slide 20 text

--jit-min-calls ● The default of --jit-min-calls is 10,000 ● You need to wait until the benchmarked path is used 10,000 times

Slide 21

Slide 21 text

The lifecycle of JIT-ed code ● MJIT's code has multiple stages: ○ Fragmented code with full optimizations ○ Fragmented code with partial optimizations ○ Compacted code with partial optimizations ● All methods should be in the last stage to see the peak performance

Slide 22

Slide 22 text

JIT recompile ● MJIT disables optimizations that didn't work and recompiles the code ● Look for "JIT recompile" shown by --jit-verbose=1 ● Your log should NOT end with "MJIT recompile" to see the peak performance

Slide 23

Slide 23 text

Optimization switches for each method ● disable_ivar_cache ● disable_exivar_cache ● disable_send_cache ● disable_inlining ● disable_const_cache

Slide 24

Slide 24 text

JIT compaction ● Once everything is compiled, MJIT schedules "JIT compaction" ● Your --jit-verbose=1 log should end with this to see the peak performance

Slide 25

Slide 25 text

The future of MRI JIT

Slide 26

Slide 26 text

Why do we have multiple JITs? ● Are we competing? ○ No, we contribute to each other's project as well ● Multi-tier JIT? ○ Efficiently mixing the code of MJIT and YJIT might be hard ○ At least MJIT needs to be replaced by MIR for better control

Slide 27

Slide 27 text

A short-term idea ● We should probably focus on YJIT ○ It is already faster and has more developers than MJIT ○ MJIT's warmup is too slow by design

Slide 28

Slide 28 text

A long-term idea ● Unblock inlining over C methods ○ YJIT cannot inline and optimize C methods as is ○ MJIT has Ruby → C inlining, but not C → Ruby yet ○ Rewrite more C methods to Ruby and/or integrate MIR

Slide 29

Slide 29 text

Conclusion ● There's a way to speed up Rails with MJIT ● We're shifting to YJIT for better performance