Upgrade to Pro — share decks privately, control downloads, hide ads and more …

ZJIT: The Future of Ruby Performance / San Fran...

ZJIT: The Future of Ruby Performance / San Francisco Ruby Conference 2025

Avatar for Takashi Kokubun

Takashi Kokubun

November 19, 2025
Tweet

More Decks by Takashi Kokubun

Other Decks in Programming

Transcript

  1. San Francisco Ruby Conference 2025 ZJIT: The Future of Ruby

    Performance Takashi Kokubun / @k0kubun
  2. self • Takashi Kokubun (@k0kubun) • Shopify: Ruby JIT team

    • Ruby committer: MJIT, RJIT, YJIT, ZJIT
  3. Why ZJIT? • Unblock cross-instruction optimizations • Less incremental, larger

    comiplation units • No memory overhead for adding optimizations
  4. How YJIT works putobject 1 getconst TWO send + leave

    Bytecode YJIT block1 Mov Reg(0), 1
  5. How YJIT works putobject 1 getconst TWO send + leave

    Bytecode YJIT block1 Mov Reg(0), 1 Context Reg(0): Integer YJIT block2 Mov Reg(1), 2 PatchPoint Constant TWO
  6. How YJIT works putobject 1 getconst TWO send + leave

    Bytecode YJIT block1 Mov Reg(0), 1 Context Reg(0): Integer YJIT block2 YJIT block3 Mov Reg(1), 2 PatchPoint Constant TWO Context Reg(0): Integer Reg(1): Integer PatchPoint Integer#+ Add Reg(0), Reg(1) Ret Reg(0)
  7. How ZJIT works putobject 1 getconst TWO zjit_send + leave

    Bytecode ZJIT HIR v1 = 1 PatchPoint TWO v2 = 2 PatchPoint Integer#+ v3 = 3 Return v3
  8. How ZJIT works putobject 1 getconst TWO zjit_send + leave

    Bytecode ZJIT HIR v1 = 1 PatchPoint TWO v2 = 2 PatchPoint Integer#+ v3 = 3 Return v3 ZJIT LIR Ret 3 PatchPoint Const TWO PatchPoint Integer#+
  9. ZJIT IR • ZJIT IR (Intermediate Representation): • HIR: High-level

    IR, new in ZJIT • LIR: Ligh-level IR, same as YJIT
  10. HIR

  11. LIR

  12. Playing with ZJIT IR • Build: con fi gure --enable-zjit

    • HIR: ruby --zjit-dump-hir • LIR: ruby --zjit-dump-lir
  13. How ZJIT compiles Ruby code one: putobject 1 leave two:

    putobject 2 leave three: putself send :one putself send :two send :+ leave Parse & Compile
  14. How ZJIT compiles Ruby code one: putobject 1 leave two:

    putobject 2 leave three: putself send :one putself send :two send :+ leave one: putobject 1 leave two: putobject 2 leave three: putself zjit_send :one putself zjit_send :two zjit_send :+ leave Parse & Compile Pro fi le
  15. How ZJIT compiles Ruby code Initial HIR: fn three@/Users/k0kubun/tmp/a.rb:11: bb0():

    EntryPoint interpreter v1:BasicObject = LoadSelf Jump bb2(v1) bb1(v4:BasicObject): EntryPoint JIT(0) Jump bb2(v4) bb2(v6:BasicObject): v11:BasicObject = SendWithoutBlock v6, :one v14:BasicObject = SendWithoutBlock v6, :two v17:BasicObject = SendWithoutBlock v11, :+, v14 CheckInterrupts Return v17 one: putobject 1 leave two: putobject 2 leave three: putself send :one putself send :two send :+ leave Compile
  16. How ZJIT compiles Ruby code Optimized HIR: fn three@/Users/k0kubun/tmp/a.rb:11: bb0():

    … bb2(v6:BasicObject): PatchPoint MethodRede fi ned(one) PatchPoint NoSingletonClass(Object) v24:HeapObject[Object] = GuardType v6, HeapObject[Object] v31:Fixnum[1] = Const Value(1) PatchPoint MethodRede fi ned(two) PatchPoint NoSingletonClass(Object) v28:HeapObject[Object] = GuardType v6, HeapObject[Object] v33:Fixnum[2] = Const Value(2) PatchPoint MethodRede fi ned(Integer, +) v38:Fixnum[3] = Const Value(3) CheckInterrupts Return v38 one: putobject 1 leave two: putobject 2 leave three: putself send :one putself send :two send :+ leave Inline
  17. How ZJIT compiles Ruby code Optimized HIR: fn three@/Users/k0kubun/tmp/a.rb:11: bb0():

    … bb2(v6:BasicObject): PatchPoint MethodRede fi ned(one) PatchPoint NoSingletonClass(Object) v24:HeapObject[Object] = GuardType v6, HeapObject[Object] v31:Fixnum[1] = Const Value(1) PatchPoint MethodRede fi ned(two) PatchPoint NoSingletonClass(Object) v28:HeapObject[Object] = GuardType v6, HeapObject[Object] v33:Fixnum[2] = Const Value(2) PatchPoint MethodRede fi ned(Integer, +) v38:Fixnum[3] = Const Value(3) CheckInterrupts Return v38 one: putobject 1 leave two: putobject 2 leave three: putself send :one putself send :two send :+ leave
  18. Future of Ruby Performance • We want less C extensions

    • In particular, less C → Ruby callbacks
  19. C functions dominate execution time Only 10% of execution time

    is spent in JIT code https://gist.github.com/k0kubun/5e0b3bb894e9fed9b01e25fd25e8bea5
  20. C functions dominate execution time Some interpreter implementations are used

    for reasons https://gist.github.com/k0kubun/5e0b3bb894e9fed9b01e25fd25e8bea5 Complicated argument setup, C → Ruby calls, megamorphic callsite Instance variables: megamorphic callsite
  21. C functions dominate execution time 15% is spent on DB

    queries https://gist.github.com/k0kubun/5e0b3bb894e9fed9b01e25fd25e8bea5
  22. C functions dominate execution time 15% is spent on allocation

    and garbage collection https://gist.github.com/k0kubun/5e0b3bb894e9fed9b01e25fd25e8bea5
  23. C functions dominate execution time Many methods are implemented in

    C https://gist.github.com/k0kubun/5e0b3bb894e9fed9b01e25fd25e8bea5
  24. C functions dominate execution time • Methods written in C

    • C → Ruby calls • Megamorphic callsites Reasons why that happens
  25. Conclusion • We're building ZJIT to unblock cross-instruction optimizations •

    We want more code to be written in Ruby for performance