Lock in $30 Savings on PRO—Offer Ends Soon! ⏳

YJIT Makes Rails 1.7x faster / RubyKaigi 2024

YJIT Makes Rails 1.7x faster / RubyKaigi 2024

Takashi Kokubun

May 17, 2024
Tweet

More Decks by Takashi Kokubun

Other Decks in Technology

Transcript

  1. ✦ Production-ready JIT for CRuby ✦ JIT: Just-In-Time Compiler ✦

    Speeds up methods that are called 30+ times YJIT
  2. ✦ --yjit ✦ RUBY_YJIT_ENABLE=1 ✦ RubyVM::YJIT.enable (Ruby 3.3+) ✦ Rails

    7.2 will call this in a default initializer Enabling YJIT
  3. ✦ --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
  4. May 15th - 17th, 2024 NAHA CULTURAL ARTS THEATER NAHArt,

    Okinawa, Japan Ruby 3.3 (and 3.4)
 YJIT optimizations
  5. ✦ What is the most impactful Ruby 3.3 YJIT optimization?

    ✦ The PR made Railsbench 7% faster Ruby 3.3 YJIT optimizations
  6. May 15th - 17th, 2024 NAHA CULTURAL ARTS THEATER NAHArt,

    Okinawa, Japan Method Call Fallbacks
  7. def foo(a) @foo = bar(a) end Ruby Code putself getlocal

    :a send :bar dup setivar @foo leave Ruby VM Instructions
  8. 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
  9. 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
  10. def foo(a) @foo = bar(**a) end Ruby Code putself getlocal

    :a send :bar, ** dup setivar @foo leave Ruby VM Instructions
  11. 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
  12. 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%
  13. 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
  14. 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
  15. ✦ 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
  16. ✦ 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
  17. 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
  18. 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
  19. 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
  20. 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
  21. 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
  22. ✦ 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
  23. 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 <main> Call Stack
  24. 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 <main> Call Stack Ruby 3.2 foo
  25. 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 <main> Call Stack Ruby 3.2 foo Array#each
  26. 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 <main> Call Stack Ruby 3.2 foo Array#each block in foo
  27. 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 <main> Call Stack Ruby 3.2 foo Array#each block in foo
  28. 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 <main> Call Stack Ruby 3.2 foo
  29. 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%
  30. 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%
  31. May 15th - 17th, 2024 NAHA CULTURAL ARTS THEATER NAHArt,

    Okinawa, Japan Register Allocator for Stack Values
  32. ✦ Ruby 3.2 YJIT writes VM stack values onto memory

    ✦ Ruby 3.3 YJIT allocates registers for them Register Allocator for Stack Values
  33. def three 1 + 2 end putobject 1 putobject 2

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

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

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

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

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

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

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

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

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

    opt_plus leave Ruby 3.3 Memory Register A Register B
  43. ✦ 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
  44. May 15th - 17th, 2024 NAHA CULTURAL ARTS THEATER NAHArt,

    Okinawa, Japan Method Inlining for Ruby
  45. ✦ 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
  46. May 15th - 17th, 2024 NAHA CULTURAL ARTS THEATER NAHArt,

    Okinawa, Japan Method Inlining for C
  47. ✦ 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
  48. ✦ 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