Slide 1

Slide 1 text

Towards Ruby 4 JIT @k0kubun

Slide 2

Slide 2 text

@k0kubun Maintain: MJIT, Haml, ERB Shopify team

Slide 3

Slide 3 text

GitHub Sponsors

Slide 4

Slide 4 text

Haml 6

Slide 5

Slide 5 text

Introduction to Ruby JIT

Slide 6

Slide 6 text

How does Ruby JIT work? Ruby

Slide 7

Slide 7 text

How does Ruby JIT work? 1 + 2 Ruby Abstract 
 Syntax 
 Tree

Slide 8

Slide 8 text

How does Ruby JIT work? 1 + 2 putobject 1 putobject 2 opt_plus leave Ruby Abstract 
 Syntax 
 Tree Instruction 
 Sequence 
 (Bytecode)

Slide 9

Slide 9 text

How does Ruby JIT work? 1 + 2 putobject 1 putobject 2 opt_plus leave Ruby Abstract 
 Syntax 
 Tree Instruction 
 Sequence 
 (Bytecode) Machine 
 Code

Slide 10

Slide 10 text

How does Ruby JIT work?

Slide 11

Slide 11 text

CRuby JIT 1: MJIT

Slide 12

Slide 12 text

CRuby JIT 2: YJIT

Slide 13

Slide 13 text

Current CRuby JITs speed.yjit.org

Slide 14

Slide 14 text

Current CRuby JITs speed.yjit.org

Slide 15

Slide 15 text

Current CRuby JITs • YJIT • Available since Ruby 3.1 • --jit or --yjit • MJIT • Available since Ruby 2.6 • --mjit

Slide 16

Slide 16 text

Current CRuby JITs • YJIT • Ruby 3.1: x86_64 only, no code GC, written in C • Ruby 3.2: arm64 support, (hopefully) code GC, written in Rust • MJIT • Ruby 3.1: Stable-ish, portable, native threads, written in C • Ruby 3.2: Experimental, fork + SIGCHLD, written in Ruby

Slide 17

Slide 17 text

MJIT in Ruby

Slide 18

Slide 18 text

No content

Slide 19

Slide 19 text

No content

Slide 20

Slide 20 text

mjit.rb: Secret "standard library" in Ruby 3.2 • mjit.rb • Even more powerful than TracePoint • You can monkey-patch CRuby JIT • No compatibility guarantee • Every module is private, so const_get is required

Slide 21

Slide 21 text

BYOJ: Bring Your Own JIT

Slide 22

Slide 22 text

BYOJ: Bring Your Own JIT • Load and pause MJIT with --mjit=pause • Define RubyVM::MJIT.compile • Use RubyVM::MJIT.const_get(:C) to hack RubyVM • Call RubyVM::MJIT.resume to start JIT With Ruby 3.2:

Slide 23

Slide 23 text

YJIT-style JIT • Monkey-patch RubyVM::MJIT.compile

Slide 24

Slide 24 text

MJIT-style JIT • Monkey-patch RubyVM::MJIT::Compiler.compile

Slide 25

Slide 25 text

MJIT-style JIT

Slide 26

Slide 26 text

Everyone is writing CRuby JIT

Slide 27

Slide 27 text

Benchmarking Ruby JIT

Slide 28

Slide 28 text

yjit-bench

Slide 29

Slide 29 text

yjit-bench • yjit-bench has three kinds of benchmarks: 1. Headlining Benchmarks 2. Other Benchmarks 3. Micro Benchmarks

Slide 30

Slide 30 text

1. Headlining benchmarks • activerecord • hexapdf • liquid-render • mail • psych-load • railsbench ✉

Slide 31

Slide 31 text

2. Other Benchmarks • binarytrees, fankuchredux, nbody • chunky_png • erubi, erubi_rails • lee • optcarrot • rubykon

Slide 32

Slide 32 text

3. Micro Benchmarks • 30k_ifelse, 30k_methods • cfunc_itself, str_concat • fib • getivar, setivar • keyword_args • respond_to

Slide 33

Slide 33 text

No content

Slide 34

Slide 34 text

Benchmark Your Own JIT • ./run_benchmarks.rb -e “/path/to/ruby --any-option” • Pass multiple -e options to compare different JITs

Slide 35

Slide 35 text

Towards Ruby 4 JIT

Slide 36

Slide 36 text

My wish on Ruby 4 JIT • I want Ruby 4 to be as fast as Java or JavaScript • Ruby 4's performance should be a reason to leave Python

Slide 37

Slide 37 text

No content

Slide 38

Slide 38 text

More Concrete Examples

Slide 39

Slide 39 text

No content

Slide 40

Slide 40 text

No content

Slide 41

Slide 41 text

No content

Slide 42

Slide 42 text

Ruby 4 Canary • true is mov-ed (immediate) • No opt_* VM instruction • Constant folding • Ruby / C method inlining

Slide 43

Slide 43 text

Ruby 4 Canary’ • Single branch instruction to access @one • Single register to access two • No heap allocation • No stack frame

Slide 44

Slide 44 text

No content

Slide 45

Slide 45 text

No content

Slide 46

Slide 46 text

Ruby 4 Canary 2 • 5000050000 is mov-ed (immediate) • Ruby -> C -> Ruby inlining

Slide 47

Slide 47 text

How can we get there?

Slide 48

Slide 48 text

Optimization Challenges 1. Constants 2. Variables 3. Method calls 4. Garbage collection

Slide 49

Slide 49 text

1. Constants

Slide 50

Slide 50 text

1. Constants

Slide 51

Slide 51 text

1. Constants

Slide 52

Slide 52 text

1. Constants

Slide 53

Slide 53 text

1. Constants

Slide 54

Slide 54 text

1. Constants

Slide 55

Slide 55 text

1. Constants

Slide 56

Slide 56 text

1. Constants

Slide 57

Slide 57 text

2. Variables

Slide 58

Slide 58 text

2. Variables

Slide 59

Slide 59 text

2. Variables

Slide 60

Slide 60 text

2. Variables

Slide 61

Slide 61 text

2. Variables

Slide 62

Slide 62 text

2. Variables

Slide 63

Slide 63 text

2. Variables

Slide 64

Slide 64 text

2. Variables

Slide 65

Slide 65 text

2. Variables

Slide 66

Slide 66 text

2. Variables

Slide 67

Slide 67 text

2. Variables

Slide 68

Slide 68 text

2. Variables 2021 2022 (tomorrow)

Slide 69

Slide 69 text

3. Method calls

Slide 70

Slide 70 text

3. Method calls

Slide 71

Slide 71 text

3. Method calls

Slide 72

Slide 72 text

3. Method calls

Slide 73

Slide 73 text

3. Method calls

Slide 74

Slide 74 text

3. Method calls

Slide 75

Slide 75 text

3. Method calls • Code locality • Method inlining: C 㱻 Ruby • Pass arguments with native ABI • Deoptimization on redefinition or interruption (or TracePoint)

Slide 76

Slide 76 text

4. Garbage collection

Slide 77

Slide 77 text

4. Garbage collection

Slide 78

Slide 78 text

4. Garbage collection

Slide 79

Slide 79 text

Next Steps • We still have a lot of rooms for improvements on yjit-bench • More cross-instruction optimizations • More method inlining over Ruby and C

Slide 80

Slide 80 text

Conclusion • Build your own JIT with Ruby 3.2 • Benchmark your JIT with yjit-bench