Slide 1

Slide 1 text

YJIT Makes Rails 1.7x Faster @k0kubun

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

YJIT Makes Rails 1.7x Faster @k0kubun 1.8x

Slide 4

Slide 4 text

@k0kubun

Slide 5

Slide 5 text

Ruby Infra team paracycle tenderlove eileencodes eightbitraptor peterzhu2118 kddnewton maximecb XrXr k0kubun nirvdrum rwstauner

Slide 6

Slide 6 text

Ruby Infra team GC Prism YJIT Lead

Slide 7

Slide 7 text

May 15th - 17th, 2024 NAHA CULTURAL ARTS THEATER NAHArt, Okinawa, Japan

Slide 8

Slide 8 text

✦ Production-ready JIT for CRuby ✦ JIT: Just-In-Time Compiler ✦ Speeds up methods that are called 30+ times YJIT

Slide 9

Slide 9 text

Shopify StoreFront
 Ruby 3.3 YJIT vs Interpreter

Slide 10

Slide 10 text

No content

Slide 11

Slide 11 text

✦ --yjit ✦ RUBY_YJIT_ENABLE=1 ✦ RubyVM::YJIT.enable (Ruby 3.3+) ✦ Rails 7.2 will call this in a default initializer Enabling YJIT

Slide 12

Slide 12 text

✦ --yjit-exec-mem-size ✦ Default: 48MiB (since Ruby 3.3.1) ✦ tip: Try 32MiB, 24MiB, or 16MiB ✦ YJIT uses 3-4x of --yjit-exec-mem-size ✦ 2-3x is used for metadata Minimizing YJIT's memory usage

Slide 13

Slide 13 text

No content

Slide 14

Slide 14 text

May 15th - 17th, 2024 NAHA CULTURAL ARTS THEATER NAHArt, Okinawa, Japan Ruby 3.3 (and 3.4)
 YJIT optimizations

Slide 15

Slide 15 text

✦ What is the most impactful Ruby 3.3 YJIT optimization? ✦ The PR made Railsbench 7% faster Ruby 3.3 YJIT optimizations

Slide 16

Slide 16 text

No content

Slide 17

Slide 17 text

No content

Slide 18

Slide 18 text

May 15th - 17th, 2024 NAHA CULTURAL ARTS THEATER NAHArt, Okinawa, Japan Method Call Fallbacks

Slide 19

Slide 19 text

def foo(a) @foo = bar(a) end Ruby Code

Slide 20

Slide 20 text

def foo(a) @foo = bar(a) end Ruby Code putself getlocal :a send :bar dup setivar @foo leave Ruby VM Instructions

Slide 21

Slide 21 text

def foo(a) @foo = bar(a) end Ruby Code putself getlocal :a send :bar dup setivar @foo leave Ruby VM Instructions JIT Code jit_putself jit_getlocal :a jit_send :bar jit_dup jit_setivar @foo jit_leave

Slide 22

Slide 22 text

def foo(a) @foo = bar(a) end Ruby Code putself getlocal :a send :bar dup setivar @foo leave Ruby VM Instructions JIT Code JIT: 100% jit_putself jit_getlocal :a jit_send :bar jit_dup jit_setivar @foo jit_leave

Slide 23

Slide 23 text

def foo(a) @foo = bar(**a) end Ruby Code putself getlocal :a send :bar, ** dup setivar @foo leave Ruby VM Instructions

Slide 24

Slide 24 text

def foo(a) @foo = bar(**a) end Ruby Code putself getlocal :a send :bar, ** dup setivar @foo leave Ruby VM Instructions JIT Code jit_putself jit_getlocal :a side_exit

Slide 25

Slide 25 text

def foo(a) @foo = bar(**a) end Ruby Code putself getlocal :a send :bar, ** dup setivar @foo leave Ruby VM Instructions JIT Code jit_putself jit_getlocal :a side_exit JIT: 33% Interpreter: 67%

Slide 26

Slide 26 text

def foo(a) @foo = bar(**a) end Ruby Code putself getlocal :a send :bar, ** dup setivar @foo leave Ruby VM Instructions JIT Code jit_putself jit_getlocal :a vm_send :bar

Slide 27

Slide 27 text

def foo(a) @foo = bar(**a) end Ruby Code putself getlocal :a send :bar, ** dup setivar @foo leave Ruby VM Instructions JIT Code JIT: 100% jit_putself jit_getlocal :a vm_send :bar jit_dup jit_setivar @foo jit_leave

Slide 28

Slide 28 text

✦ Ruby 3.3: Use the interpreter’s C function (like MJIT) for: ✦ Unsupported calls ✦ Megamorphic calls ✦ Fallbacks allow the subsequent code to be compiled Method Call Fallbacks

Slide 29

Slide 29 text

✦ Ruby 3.3: Use the interpreter’s C function (like MJIT) for: ✦ Unsupported calls ✦ Megamorphic calls ← What’s this? ✦ Fallbacks allow the subsequent code to be compiled Method Call Fallbacks

Slide 30

Slide 30 text

class X def call @x = x end end class A < X def x = :a end :a if self.class == A Ruby Code Ruby 3.2 YJIT

Slide 31

Slide 31 text

class X def call @x = x end end class A < X def x = :a end class B < X def x = :b end :a if self.class == A Ruby Code Ruby 3.2 YJIT :b if self.class == B

Slide 32

Slide 32 text

class X def call @x = x end end class A < X def x = :a end class B < X def x = :b end class C < X def x = :c end :a if self.class == A Ruby Code Ruby 3.2 YJIT :b if self.class == B :c if self.class == C

Slide 33

Slide 33 text

class X def call @x = x end end class A < X def x = :a end class B < X def x = :b end class C < X def x = :c end class D < X def x = :d end :a if self.class == A Ruby Code Ruby 3.2 YJIT :b if self.class == B :c if self.class == C side exit

Slide 34

Slide 34 text

class X def call @x = x end end class A < X def x = :a end class B < X def x = :b end class C < X def x = :c end class D < X def x = :d end :a if self.class == A Ruby Code Ruby 3.2 YJIT :b if self.class == B :c if self.class == C side exit :a if self.class == A :b if self.class == B :c if self.class == C vm_send :x Ruby 3.3 YJIT setivar @x

Slide 35

Slide 35 text

May 15th - 17th, 2024 NAHA CULTURAL ARTS THEATER NAHArt, Okinawa, Japan Exception Handlers

Slide 36

Slide 36 text

No content

Slide 37

Slide 37 text

✦ In CRuby, exception handlers are executed differently from regular methods ✦ Ruby 3.3 YJIT added support for them in CRuby JITs for the first time ✦ "Exceptions" are not necessarily Ruby exceptions, e.g. break/return in blocks Exception Handlers

Slide 38

Slide 38 text

def foo(arr) @foo = arr.each do |a| break a if a.odd? end end Ruby 3.2

Slide 39

Slide 39 text

def foo(arr) @foo = arr.each do |a| break a if a.odd? end end getlocal arr send :each, block dup setivar @foo leave getlocal a send :odd? branchunless L0 getlocal a throw TAG_BREAK leave L0: putnil leave Ruby 3.2 Call Stack

Slide 40

Slide 40 text

def foo(arr) @foo = arr.each do |a| break a if a.odd? end end getlocal arr send :each, block dup setivar @foo leave getlocal a send :odd? branchunless L0 getlocal a throw TAG_BREAK leave L0: putnil leave Call Stack Ruby 3.2 foo

Slide 41

Slide 41 text

def foo(arr) @foo = arr.each do |a| break a if a.odd? end end getlocal arr send :each, block dup setivar @foo leave getlocal a send :odd? branchunless L0 getlocal a throw TAG_BREAK leave L0: putnil leave Call Stack Ruby 3.2 foo Array#each

Slide 42

Slide 42 text

def foo(arr) @foo = arr.each do |a| break a if a.odd? end end getlocal arr send :each, block dup setivar @foo leave getlocal a send :odd? branchunless L0 getlocal a throw TAG_BREAK leave L0: putnil leave Call Stack Ruby 3.2 foo Array#each block in foo

Slide 43

Slide 43 text

def foo(arr) @foo = arr.each do |a| break a if a.odd? end end getlocal arr send :each, block dup setivar @foo leave getlocal a send :odd? branchunless L0 getlocal a throw TAG_BREAK leave L0: putnil leave Call Stack Ruby 3.2 foo Array#each block in foo

Slide 44

Slide 44 text

def foo(arr) @foo = arr.each do |a| break a if a.odd? end end getlocal arr send :each, block dup setivar @foo leave getlocal a send :odd? branchunless L0 getlocal a throw TAG_BREAK leave L0: putnil leave Call Stack Ruby 3.2 foo

Slide 45

Slide 45 text

def foo(arr) @foo = arr.each do |a| break a if a.odd? end end getlocal arr send :each, block dup setivar @foo leave getlocal a send :odd? branchunless L0 getlocal a throw TAG_BREAK leave L0: putnil leave Ruby 3.2 JIT: 60% Interpreter: 40%

Slide 46

Slide 46 text

def foo(arr) @foo = arr.each do |a| break a if a.odd? end end getlocal arr send :each, block dup setivar @foo leave getlocal a send :odd? branchunless L0 getlocal a throw TAG_BREAK leave L0: putnil leave Ruby 3.3 JIT: 100%

Slide 47

Slide 47 text

May 15th - 17th, 2024 NAHA CULTURAL ARTS THEATER NAHArt, Okinawa, Japan Register Allocator for Stack Values

Slide 48

Slide 48 text

No content

Slide 49

Slide 49 text

✦ Ruby 3.2 YJIT writes VM stack values onto memory ✦ Ruby 3.3 YJIT allocates registers for them Register Allocator for Stack Values

Slide 50

Slide 50 text

def three 1 + 2 end Ruby 3.2

Slide 51

Slide 51 text

def three 1 + 2 end putobject 1 putobject 2 opt_plus leave Ruby 3.2 Memory Register A Register B

Slide 52

Slide 52 text

def three 1 + 2 end putobject 1 putobject 2 opt_plus leave Ruby 3.2 Memory Register A Register B 1

Slide 53

Slide 53 text

def three 1 + 2 end putobject 1 putobject 2 opt_plus leave Ruby 3.2 Memory Register A Register B 1 2

Slide 54

Slide 54 text

def three 1 + 2 end putobject 1 putobject 2 opt_plus leave Ruby 3.2 Memory Register A Register B 3

Slide 55

Slide 55 text

def three 1 + 2 end putobject 1 putobject 2 opt_plus leave Ruby 3.2 Memory Register A Register B

Slide 56

Slide 56 text

def three 1 + 2 end putobject 1 putobject 2 opt_plus leave Ruby 3.3 Memory Register A Register B

Slide 57

Slide 57 text

def three 1 + 2 end putobject 1 putobject 2 opt_plus leave Ruby 3.3 Memory Register A Register B 1

Slide 58

Slide 58 text

def three 1 + 2 end putobject 1 putobject 2 opt_plus leave Ruby 3.3 Memory Register A Register B 1 2

Slide 59

Slide 59 text

def three 1 + 2 end putobject 1 putobject 2 opt_plus leave Ruby 3.3 Memory Register A Register B 3

Slide 60

Slide 60 text

def three 1 + 2 end putobject 1 putobject 2 opt_plus leave Ruby 3.3 Memory Register A Register B

Slide 61

Slide 61 text

✦ Ruby 3.4 YJIT: Optimize local variables like stack values, speculating Kernel#binding is not called ✦ Ruby 3.4 will hopefully allocate registers for them too (under development) Register Allocator for Local Variables

Slide 62

Slide 62 text

May 15th - 17th, 2024 NAHA CULTURAL ARTS THEATER NAHArt, Okinawa, Japan Method Inlining for Ruby

Slide 63

Slide 63 text

No content

Slide 64

Slide 64 text

✦ YJIT 3.3+ inlines single-line methods that return: ✦ Ruby 3.3: 1, 2, 3, …, true, false, nil, :symbol ✦ Ruby 3.4: self, local variables Method Inlining for Ruby

Slide 65

Slide 65 text

Active
 Support

Slide 66

Slide 66 text

Active
 Support

Slide 67

Slide 67 text

Active
 Support

Slide 68

Slide 68 text

Symbol#to_sym

Slide 69

Slide 69 text

Sorbet Helper Patches

Slide 70

Slide 70 text

May 15th - 17th, 2024 NAHA CULTURAL ARTS THEATER NAHArt, Okinawa, Japan Method Inlining for C

Slide 71

Slide 71 text

No content

Slide 72

Slide 72 text

✦ Ruby 3.3 YJIT specializes many C methods, which leads to a high C method inlining ratio ✦ Ruby 3.4 YJIT pushes "lazy frames" Method Inlining for C

Slide 73

Slide 73 text

No content

Slide 74

Slide 74 text

No content

Slide 75

Slide 75 text

No content

Slide 76

Slide 76 text

✦ Enable YJIT in production now! ✦ YJIT speeds up production apps by 10-20% ✦ Upgrade Ruby to 3.3 ✦ JIT more code, allocate registers, and inline method calls Conclusion