Skip to content

ZJIT: Speed up Rails benchmarks #713

Description

@k0kubun

This issue aims to make ZJIT as fast as YJIT on the following benchmarks in yjit-bench:

  • railsbench
  • lobsters
  • shipit

activerecord and erubi-rails should also be improved. But it's nice to focus on realistic, whole-application benchmarks.

Burndown list

Reduce side exits

ratio_in_zjit is still 72%. This should be the top priority until we increase it to 98-99% or more. When it's low, it not only becomes slower due to cache thrashing but also randomly misses already-implemented optimizations.

We should look at "side exit reasons" and its sub-categories like "compile error reasons" and "unhandled YARV insns", and fix the ones that happen most frequently.

Reduce dynamic sends

Method calls are a major source of potential speedups. Even if you fully materialize a frame like YJIT does, they can still be a lot faster than the interpreter (at least as fast as YJIT) by reducing memory reads and removing function calls.

We should look at "send fallback reasons" and its sub-categories like "not optimized instructions" and "not optimized method types", and fix the ones that happen most frequently.

Reduce things slower than YJIT

There are overheads in ZJIT that YJIT doesn't have. To make ZJIT faster than YJIT, we need to remove them, or at least minimize their impact.

ZJIT stats

ZJIT stats on lobsters as of 2025-10-17
***ZJIT: Printing ZJIT statistics on exit***
Top-20 not inlined C methods (61.1% of total 10,568,483):
                               Kernel#is_a?: 1,030,761 ( 9.8%)
                                  String#<<:   851,954 ( 8.1%)
                                   Hash#[]=:   742,941 ( 7.0%)
                              Regexp#match?:   399,888 ( 3.8%)
                                  Hash#key?:   349,146 ( 3.3%)
                         String#start_with?:   334,961 ( 3.2%)
                         Kernel#respond_to?:   316,527 ( 3.0%)
                 ObjectSpace::WeakKeyMap#[]:   238,978 ( 2.3%)
                              TrueClass#===:   235,771 ( 2.2%)
                             FalseClass#===:   231,144 ( 2.2%)
                             Array#include?:   211,385 ( 2.0%)
                                 Hash#fetch:   204,702 ( 1.9%)
                        Kernel#block_given?:   181,797 ( 1.7%)
                                 Kernel#dup:   179,341 ( 1.7%)
                             BasicObject#!=:   175,997 ( 1.7%)
                                 String.new:   166,696 ( 1.6%)
                            Kernel#kind_of?:   165,600 ( 1.6%)
                                  String#==:   157,735 ( 1.5%)
                      Process.clock_gettime:   144,992 ( 1.4%)
                                 Array#any?:   138,310 ( 1.3%)
Top-20 not annotated C methods (62.1% of total 10,723,378):
                               Kernel#is_a?: 1,212,652 (11.3%)
                                  String#<<:   851,954 ( 7.9%)
                                   Hash#[]=:   743,120 ( 6.9%)
                              Regexp#match?:   399,888 ( 3.7%)
                                  Hash#key?:   349,146 ( 3.3%)
                         String#start_with?:   334,961 ( 3.1%)
                         Kernel#respond_to?:   316,527 ( 3.0%)
                 ObjectSpace::WeakKeyMap#[]:   238,978 ( 2.2%)
                              TrueClass#===:   235,771 ( 2.2%)
                             FalseClass#===:   231,144 ( 2.2%)
                             Array#include?:   211,385 ( 2.0%)
                                 Hash#fetch:   204,702 ( 1.9%)
                        Kernel#block_given?:   191,666 ( 1.8%)
                                 Kernel#dup:   179,348 ( 1.7%)
                             BasicObject#!=:   176,181 ( 1.6%)
                                 String.new:   166,696 ( 1.6%)
                            Kernel#kind_of?:   165,634 ( 1.5%)
                                  String#==:   163,667 ( 1.5%)
                      Process.clock_gettime:   144,992 ( 1.4%)
                                 Array#any?:   138,310 ( 1.3%)
Top-2 not optimized method types for send (100.0% of total 72,318):
  cfunc: 48,055 (66.4%)
   iseq: 24,263 (33.6%)
Top-6 not optimized method types for send_without_block (100.0% of total 4,523,682):
       iseq: 2,271,935 (50.2%)
    bmethod:   985,636 (21.8%)
  optimized:   949,704 (21.0%)
      alias:   310,747 ( 6.9%)
       null:     5,106 ( 0.1%)
      cfunc:       554 ( 0.0%)
Top-13 not optimized instructions (100.0% of total 4,293,170):
             invokesuper: 2,373,404 (55.3%)
             invokeblock:   811,926 (18.9%)
             sendforward:   505,452 (11.8%)
                  opt_eq:   451,754 (10.5%)
                opt_plus:    74,404 ( 1.7%)
               opt_minus:    36,228 ( 0.8%)
  opt_send_without_block:    21,791 ( 0.5%)
                 opt_neq:     7,231 ( 0.2%)
                opt_mult:     6,752 ( 0.2%)
                  opt_or:     3,753 ( 0.1%)
                  opt_lt:       348 ( 0.0%)
                  opt_ge:        91 ( 0.0%)
                  opt_gt:        36 ( 0.0%)
Top-9 send fallback reasons (100.0% of total 25,480,950):
                send_without_block_polymorphic: 9,722,492 (38.2%)
                              send_no_profiles: 5,894,788 (23.1%)
  send_without_block_not_optimized_method_type: 4,523,682 (17.8%)
                     not_optimized_instruction: 4,293,170 (16.8%)
                send_without_block_no_profiles:   948,972 ( 3.7%)
                send_not_optimized_method_type:    72,318 ( 0.3%)
       send_without_block_cfunc_array_variadic:    15,134 ( 0.1%)
                      obj_to_string_not_string:     9,765 ( 0.0%)
       send_without_block_direct_too_many_args:       629 ( 0.0%)
Top-9 unhandled YARV insns (100.0% of total 690,950):
         expandarray: 328,490 (47.5%)
        checkkeyword: 190,694 (27.6%)
    getclassvariable:  59,901 ( 8.7%)
  invokesuperforward:  49,503 ( 7.2%)
       getblockparam:  49,119 ( 7.1%)
   opt_duparray_send:  11,978 ( 1.7%)
         getconstant:     952 ( 0.1%)
          checkmatch:     290 ( 0.0%)
                once:      23 ( 0.0%)
Top-3 compile error reasons (100.0% of total 3,718,619):
  register_spill_on_alloc: 3,418,255 (91.9%)
  register_spill_on_ccall:   182,018 ( 4.9%)
        exception_handler:   118,346 ( 3.2%)
Top-17 side exit reasons (100.0% of total 10,860,358):
                        compile_error: 3,718,619 (34.2%)
                   guard_type_failure: 2,638,914 (24.3%)
                  guard_shape_failure: 1,917,205 (17.7%)
                  unhandled_yarv_insn:   690,950 ( 6.4%)
  block_param_proxy_not_iseq_or_ifunc:   535,789 ( 4.9%)
                      unhandled_kwarg:   455,348 ( 4.2%)
                unknown_newarray_send:   314,788 ( 2.9%)
     patchpoint_stable_constant_names:   235,469 ( 2.2%)
                      unhandled_splat:   122,071 ( 1.1%)
        patchpoint_no_singleton_class:   109,652 ( 1.0%)
                   unhandled_hir_insn:    76,397 ( 0.7%)
          patchpoint_method_redefined:    21,590 ( 0.2%)
           block_param_proxy_modified:    19,193 ( 0.2%)
              patchpoint_no_ep_escape:     3,765 ( 0.0%)
               obj_to_string_fallback:       566 ( 0.0%)
               guard_type_not_failure:        22 ( 0.0%)
                            interrupt:        20 ( 0.0%)
                             send_count: 68,203,448
                     dynamic_send_count: 25,480,950 (37.4%)
                   optimized_send_count: 42,722,498 (62.6%)
              iseq_optimized_send_count: 18,587,492 (27.3%)
      inline_cfunc_optimized_send_count: 13,566,523 (19.9%)
non_variadic_cfunc_optimized_send_count:  7,904,314 (11.6%)
    variadic_cfunc_optimized_send_count:  2,664,169 ( 3.9%)
dynamic_getivar_count:                        7,365,994
dynamic_setivar_count:                        7,244,999
compiled_iseq_count:                              4,796
failed_iseq_count:                                  447
compile_time:                                     779ms
profile_time:                                       8ms
gc_time:                                           11ms
invalidation_time:                                 75ms
vm_write_pc_count:                           63,634,930
vm_write_sp_count:                           62,291,157
vm_write_locals_count:                       62,291,157
vm_write_stack_count:                        62,291,157
vm_write_to_parent_iseq_local_count:            292,458
vm_read_from_parent_iseq_local_count:         6,599,701
code_region_bytes:                           22,970,368
side_exit_count:                             10,860,358
total_insn_count:                           517,605,935
vm_insn_count:                              162,979,594
zjit_insn_count:                            354,626,341
ratio_in_zjit:                                    68.5%
ZJIT stats on railsbench as of 2025-10-17
***ZJIT: Printing ZJIT statistics on exit***
Top-20 not inlined C methods (63.9% of total 22,558,282):
                                   Hash#[]=: 1,700,237 ( 7.5%)
                                  String#<<: 1,494,022 ( 6.6%)
                               Kernel#is_a?: 1,429,926 ( 6.3%)
                              Regexp#match?: 1,235,064 ( 5.5%)
                         Kernel#respond_to?: 1,198,251 ( 5.3%)
                                  Hash#key?: 1,087,406 ( 4.8%)
                             String#setbyte:   810,022 ( 3.6%)
                                  Integer#^:   766,624 ( 3.4%)
                        Kernel#block_given?:   603,613 ( 2.7%)
                                  String#==:   590,408 ( 2.6%)
                                 String.new:   482,204 ( 2.1%)
                                Hash#delete:   455,288 ( 2.0%)
                             BasicObject#!=:   428,771 ( 1.9%)
                                 Hash#fetch:   408,621 ( 1.8%)
                         String#ascii_only?:   373,915 ( 1.7%)
                 ObjectSpace::WeakKeyMap#[]:   287,957 ( 1.3%)
                               NilClass#===:   277,244 ( 1.2%)
                               Kernel#Array:   269,590 ( 1.2%)
                                 Array#join:   260,047 ( 1.2%)
                             Array#include?:   247,816 ( 1.1%)
Top-20 not annotated C methods (64.6% of total 22,425,847):
                                   Hash#[]=: 1,700,416 ( 7.6%)
                               Kernel#is_a?: 1,515,668 ( 6.8%)
                                  String#<<: 1,494,022 ( 6.7%)
                              Regexp#match?: 1,235,064 ( 5.5%)
                         Kernel#respond_to?: 1,198,251 ( 5.3%)
                                  Hash#key?: 1,087,406 ( 4.8%)
                             String#setbyte:   810,022 ( 3.6%)
                                  Integer#^:   766,624 ( 3.4%)
                        Kernel#block_given?:   603,613 ( 2.7%)
                                  String#==:   601,114 ( 2.7%)
                                 String.new:   482,204 ( 2.2%)
                                Hash#delete:   455,288 ( 2.0%)
                             BasicObject#!=:   428,876 ( 1.9%)
                                 Hash#fetch:   408,621 ( 1.8%)
                         String#ascii_only?:   373,915 ( 1.7%)
                 ObjectSpace::WeakKeyMap#[]:   287,957 ( 1.3%)
                               NilClass#===:   277,244 ( 1.2%)
                               Kernel#Array:   269,590 ( 1.2%)
                             Array#include?:   247,816 ( 1.1%)
                                 Kernel#dup:   239,887 ( 1.1%)
Top-2 not optimized method types for send (100.0% of total 186,159):
   iseq: 112,747 (60.6%)
  cfunc:  73,412 (39.4%)
Top-6 not optimized method types for send_without_block (100.0% of total 8,142,248):
       iseq: 3,464,671 (42.6%)
  optimized: 2,632,884 (32.3%)
    bmethod: 1,290,701 (15.9%)
      alias:   706,020 ( 8.7%)
       null:    47,942 ( 0.6%)
      cfunc:        30 ( 0.0%)
Top-11 not optimized instructions (100.0% of total 8,394,873):
             invokesuper: 5,602,274 (66.7%)
             invokeblock: 1,764,936 (21.0%)
             sendforward:   551,832 ( 6.6%)
                  opt_eq:   441,959 ( 5.3%)
                opt_plus:    31,635 ( 0.4%)
  opt_send_without_block:     1,163 ( 0.0%)
                  opt_lt:       372 ( 0.0%)
                opt_mult:       251 ( 0.0%)
                  opt_ge:       193 ( 0.0%)
                 opt_neq:       149 ( 0.0%)
                  opt_or:       109 ( 0.0%)
Top-8 send fallback reasons (100.0% of total 40,619,436):
                send_without_block_polymorphic: 12,933,921 (31.8%)
                              send_no_profiles:  9,033,636 (22.2%)
                     not_optimized_instruction:  8,394,873 (20.7%)
  send_without_block_not_optimized_method_type:  8,142,248 (20.0%)
                send_without_block_no_profiles:  1,709,913 ( 4.2%)
       send_without_block_cfunc_array_variadic:    215,046 ( 0.5%)
                send_not_optimized_method_type:    186,159 ( 0.5%)
                      obj_to_string_not_string:      3,640 ( 0.0%)
Top-9 unhandled YARV insns (100.0% of total 1,604,456):
    getclassvariable: 458,136 (28.6%)
       getblockparam: 455,921 (28.4%)
        checkkeyword: 265,425 (16.5%)
  invokesuperforward: 239,383 (14.9%)
         expandarray: 137,305 ( 8.6%)
         getconstant:  48,100 ( 3.0%)
          checkmatch:     149 ( 0.0%)
                once:      23 ( 0.0%)
   opt_duparray_send:      14 ( 0.0%)
Top-3 compile error reasons (100.0% of total 5,570,130):
  register_spill_on_alloc: 4,994,130 (89.7%)
        exception_handler:   356,784 ( 6.4%)
  register_spill_on_ccall:   219,216 ( 3.9%)
Top-16 side exit reasons (100.0% of total 12,412,191):
                        compile_error: 5,570,130 (44.9%)
                  unhandled_yarv_insn: 1,604,456 (12.9%)
                  guard_shape_failure: 1,462,883 (11.8%)
                   guard_type_failure:   845,891 ( 6.8%)
  block_param_proxy_not_iseq_or_ifunc:   765,968 ( 6.2%)
                      unhandled_kwarg:   658,341 ( 5.3%)
                      unhandled_splat:   446,990 ( 3.6%)
     patchpoint_stable_constant_names:   397,594 ( 3.2%)
                unknown_newarray_send:   332,740 ( 2.7%)
                   unhandled_hir_insn:   160,205 ( 1.3%)
          patchpoint_method_redefined:   106,687 ( 0.9%)
           block_param_proxy_modified:    59,589 ( 0.5%)
               obj_to_string_fallback:       553 ( 0.0%)
              patchpoint_no_ep_escape:       155 ( 0.0%)
                            interrupt:         8 ( 0.0%)
        patchpoint_no_singleton_class:         1 ( 0.0%)
                             send_count: 133,391,539
                     dynamic_send_count:  40,619,436 (30.5%)
                   optimized_send_count:  92,772,103 (69.5%)
              iseq_optimized_send_count:  39,936,536 (29.9%)
      inline_cfunc_optimized_send_count:  30,277,285 (22.7%)
non_variadic_cfunc_optimized_send_count:  16,755,937 (12.6%)
    variadic_cfunc_optimized_send_count:   5,802,345 ( 4.3%)
dynamic_getivar_count:                       10,980,323
dynamic_setivar_count:                       12,962,722
compiled_iseq_count:                              2,530
failed_iseq_count:                                  245
compile_time:                                     402ms
profile_time:                                      19ms
gc_time:                                           33ms
invalidation_time:                                  4ms
vm_write_pc_count:                          125,997,735
vm_write_sp_count:                          122,927,105
vm_write_locals_count:                      122,927,105
vm_write_stack_count:                       122,927,105
vm_write_to_parent_iseq_local_count:            385,461
vm_read_from_parent_iseq_local_count:        11,266,484
code_region_bytes:                           12,156,928
side_exit_count:                             12,412,191
total_insn_count:                           866,779,936
vm_insn_count:                              216,821,024
zjit_insn_count:                            649,958,912
ratio_in_zjit:                                    75.0%

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions