diff options
| author | Max Bernstein <rubybugs@bernsteinbear.com> | 2025-11-26 15:36:00 -0800 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-11-26 15:36:00 -0800 |
| commit | db94a79da432bcdb9d48517733f11ccf03c7cd5d (patch) | |
| tree | 3972c411032211b42a96dbae2e7ccb23af264716 | |
| parent | 52426a22de8ce5e2f02ce4169a1b6944e2165b46 (diff) | |
ZJIT: Count fallback reasons for set/get/definedivar (#15324)
lobsters:
```
Top-4 setivar fallback reasons (100.0% of total 7,789,008):
shape_transition: 6,074,085 (78.0%)
not_monomorphic: 1,484,013 (19.1%)
not_t_object: 172,629 ( 2.2%)
too_complex: 58,281 ( 0.7%)
Top-3 getivar fallback reasons (100.0% of total 9,348,832):
not_t_object: 4,658,833 (49.8%)
not_monomorphic: 4,542,316 (48.6%)
too_complex: 147,683 ( 1.6%)
Top-3 definedivar fallback reasons (100.0% of total 366,383):
not_monomorphic: 361,389 (98.6%)
too_complex: 3,062 ( 0.8%)
not_t_object: 1,932 ( 0.5%)
```
railsbench:
```
Top-3 setivar fallback reasons (100.0% of total 15,119,057):
shape_transition: 13,760,763 (91.0%)
not_monomorphic: 982,368 ( 6.5%)
not_t_object: 375,926 ( 2.5%)
Top-2 getivar fallback reasons (100.0% of total 14,438,747):
not_t_object: 7,643,870 (52.9%)
not_monomorphic: 6,794,877 (47.1%)
Top-2 definedivar fallback reasons (100.0% of total 209,613):
not_monomorphic: 209,526 (100.0%)
not_t_object: 87 ( 0.0%)
```
shipit:
```
Top-3 setivar fallback reasons (100.0% of total 14,516,254):
shape_transition: 8,613,512 (59.3%)
not_monomorphic: 5,761,398 (39.7%)
not_t_object: 141,344 ( 1.0%)
Top-2 getivar fallback reasons (100.0% of total 21,016,444):
not_monomorphic: 11,313,482 (53.8%)
not_t_object: 9,702,962 (46.2%)
Top-2 definedivar fallback reasons (100.0% of total 290,382):
not_monomorphic: 287,755 (99.1%)
not_t_object: 2,627 ( 0.9%)
```
| -rw-r--r-- | zjit.rb | 9 | ||||
| -rw-r--r-- | zjit/src/codegen.rs | 2 | ||||
| -rw-r--r-- | zjit/src/hir.rs | 14 | ||||
| -rw-r--r-- | zjit/src/hir/opt_tests.rs | 15 | ||||
| -rw-r--r-- | zjit/src/stats.rs | 99 |
5 files changed, 128 insertions, 11 deletions
@@ -183,6 +183,9 @@ class << RubyVM::ZJIT print_counters_with_prefix(prefix: 'unspecialized_send_without_block_def_type_', prompt: 'not optimized method types for send_without_block', buf:, stats:, limit: 20) print_counters_with_prefix(prefix: 'uncategorized_fallback_yarv_insn_', prompt: 'instructions with uncategorized fallback reason', buf:, stats:, limit: 20) print_counters_with_prefix(prefix: 'send_fallback_', prompt: 'send fallback reasons', buf:, stats:, limit: 20) + print_counters_with_prefix(prefix: 'setivar_fallback_', prompt: 'setivar fallback reasons', buf:, stats:, limit: 5) + print_counters_with_prefix(prefix: 'getivar_fallback_', prompt: 'getivar fallback reasons', buf:, stats:, limit: 5) + print_counters_with_prefix(prefix: 'definedivar_fallback_', prompt: 'definedivar fallback reasons', buf:, stats:, limit: 5) print_counters_with_prefix(prefix: 'invokeblock_handler_', prompt: 'invokeblock handler', buf:, stats:, limit: 10) # Show most popular unsupported call features. Because each call can @@ -201,6 +204,9 @@ class << RubyVM::ZJIT :send_count, :dynamic_send_count, :optimized_send_count, + :dynamic_setivar_count, + :dynamic_getivar_count, + :dynamic_definedivar_count, :iseq_optimized_send_count, :inline_cfunc_optimized_send_count, :inline_iseq_optimized_send_count, @@ -208,9 +214,6 @@ class << RubyVM::ZJIT :variadic_cfunc_optimized_send_count, ], buf:, stats:, right_align: true, base: :send_count) print_counters([ - :dynamic_getivar_count, - :dynamic_setivar_count, - :compiled_iseq_count, :failed_iseq_count, diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 3300219ccd..66436b2374 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -892,7 +892,6 @@ fn gen_ccall_variadic( /// Emit an uncached instance variable lookup fn gen_getivar(jit: &mut JITState, asm: &mut Assembler, recv: Opnd, id: ID, ic: *const iseq_inline_iv_cache_entry) -> Opnd { - gen_incr_counter(asm, Counter::dynamic_getivar_count); if ic.is_null() { asm_ccall!(asm, rb_ivar_get, recv, id.0.into()) } else { @@ -903,7 +902,6 @@ fn gen_getivar(jit: &mut JITState, asm: &mut Assembler, recv: Opnd, id: ID, ic: /// Emit an uncached instance variable store fn gen_setivar(jit: &mut JITState, asm: &mut Assembler, recv: Opnd, id: ID, ic: *const iseq_inline_iv_cache_entry, val: Opnd, state: &FrameState) { - gen_incr_counter(asm, Counter::dynamic_setivar_count); // Setting an ivar can raise FrozenError, so we need proper frame state for exception handling. gen_prepare_non_leaf_call(jit, asm, state); if ic.is_null() { diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index b7149ad14c..fbc9d80700 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -2806,19 +2806,23 @@ impl Function { let frame_state = self.frame_state(state); let Some(recv_type) = self.profiled_type_of_at(self_val, frame_state.insn_idx) else { // No (monomorphic/skewed polymorphic) profile info + self.push_insn(block, Insn::IncrCounter(Counter::getivar_fallback_not_monomorphic)); self.push_insn_id(block, insn_id); continue; }; if recv_type.flags().is_immediate() { // Instance variable lookups on immediate values are always nil + self.push_insn(block, Insn::IncrCounter(Counter::getivar_fallback_immediate)); self.push_insn_id(block, insn_id); continue; } assert!(recv_type.shape().is_valid()); if !recv_type.flags().is_t_object() { // Check if the receiver is a T_OBJECT + self.push_insn(block, Insn::IncrCounter(Counter::getivar_fallback_not_t_object)); self.push_insn_id(block, insn_id); continue; } if recv_type.shape().is_too_complex() { // too-complex shapes can't use index access + self.push_insn(block, Insn::IncrCounter(Counter::getivar_fallback_too_complex)); self.push_insn_id(block, insn_id); continue; } let self_val = self.push_insn(block, Insn::GuardType { val: self_val, guard_type: types::HeapBasicObject, state }); @@ -2845,19 +2849,23 @@ impl Function { let frame_state = self.frame_state(state); let Some(recv_type) = self.profiled_type_of_at(self_val, frame_state.insn_idx) else { // No (monomorphic/skewed polymorphic) profile info + self.push_insn(block, Insn::IncrCounter(Counter::definedivar_fallback_not_monomorphic)); self.push_insn_id(block, insn_id); continue; }; if recv_type.flags().is_immediate() { // Instance variable lookups on immediate values are always nil + self.push_insn(block, Insn::IncrCounter(Counter::definedivar_fallback_immediate)); self.push_insn_id(block, insn_id); continue; } assert!(recv_type.shape().is_valid()); if !recv_type.flags().is_t_object() { // Check if the receiver is a T_OBJECT + self.push_insn(block, Insn::IncrCounter(Counter::definedivar_fallback_not_t_object)); self.push_insn_id(block, insn_id); continue; } if recv_type.shape().is_too_complex() { // too-complex shapes can't use index access + self.push_insn(block, Insn::IncrCounter(Counter::definedivar_fallback_too_complex)); self.push_insn_id(block, insn_id); continue; } let self_val = self.push_insn(block, Insn::GuardType { val: self_val, guard_type: types::HeapBasicObject, state }); @@ -2877,28 +2885,34 @@ impl Function { let frame_state = self.frame_state(state); let Some(recv_type) = self.profiled_type_of_at(self_val, frame_state.insn_idx) else { // No (monomorphic/skewed polymorphic) profile info + self.push_insn(block, Insn::IncrCounter(Counter::setivar_fallback_not_monomorphic)); self.push_insn_id(block, insn_id); continue; }; if recv_type.flags().is_immediate() { // Instance variable lookups on immediate values are always nil + self.push_insn(block, Insn::IncrCounter(Counter::setivar_fallback_immediate)); self.push_insn_id(block, insn_id); continue; } assert!(recv_type.shape().is_valid()); if !recv_type.flags().is_t_object() { // Check if the receiver is a T_OBJECT + self.push_insn(block, Insn::IncrCounter(Counter::setivar_fallback_not_t_object)); self.push_insn_id(block, insn_id); continue; } if recv_type.shape().is_too_complex() { // too-complex shapes can't use index access + self.push_insn(block, Insn::IncrCounter(Counter::setivar_fallback_too_complex)); self.push_insn_id(block, insn_id); continue; } if recv_type.shape().is_frozen() { // Can't set ivars on frozen objects + self.push_insn(block, Insn::IncrCounter(Counter::setivar_fallback_frozen)); self.push_insn_id(block, insn_id); continue; } let mut ivar_index: u16 = 0; if !unsafe { rb_shape_get_iv_index(recv_type.shape().0, id, &mut ivar_index) } { // TODO(max): Shape transition + self.push_insn(block, Insn::IncrCounter(Counter::setivar_fallback_shape_transition)); self.push_insn_id(block, insn_id); continue; } let self_val = self.push_insn(block, Insn::GuardType { val: self_val, guard_type: types::HeapBasicObject, state }); diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index c460942fc1..b32da5a9eb 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -3343,6 +3343,7 @@ mod hir_opt_tests { Jump bb2(v4) bb2(v6:BasicObject): PatchPoint SingleRactorMode + IncrCounter getivar_fallback_not_monomorphic v11:BasicObject = GetIvar v6, :@foo CheckInterrupts Return v11 @@ -3366,6 +3367,7 @@ mod hir_opt_tests { bb2(v6:BasicObject): v10:Fixnum[1] = Const Value(1) PatchPoint SingleRactorMode + IncrCounter setivar_fallback_not_monomorphic SetIvar v6, :@foo, v10 CheckInterrupts Return v10 @@ -3442,6 +3444,7 @@ mod hir_opt_tests { EntryPoint JIT(0) Jump bb2(v4) bb2(v6:BasicObject): + IncrCounter definedivar_fallback_not_t_object v10:StringExact|NilClass = DefinedIvar v6, :@a CheckInterrupts Return v10 @@ -3473,6 +3476,7 @@ mod hir_opt_tests { EntryPoint JIT(0) Jump bb2(v4) bb2(v6:BasicObject): + IncrCounter definedivar_fallback_not_monomorphic v10:StringExact|NilClass = DefinedIvar v6, :@a CheckInterrupts Return v10 @@ -3525,6 +3529,7 @@ mod hir_opt_tests { bb2(v6:BasicObject): v10:Fixnum[5] = Const Value(5) PatchPoint SingleRactorMode + IncrCounter setivar_fallback_shape_transition SetIvar v6, :@foo, v10 CheckInterrupts Return v10 @@ -3554,6 +3559,7 @@ mod hir_opt_tests { bb2(v6:BasicObject): v10:Fixnum[5] = Const Value(5) PatchPoint SingleRactorMode + IncrCounter setivar_fallback_not_t_object SetIvar v6, :@a, v10 CheckInterrupts Return v10 @@ -3587,6 +3593,7 @@ mod hir_opt_tests { bb2(v6:BasicObject): v10:Fixnum[5] = Const Value(5) PatchPoint SingleRactorMode + IncrCounter setivar_fallback_not_monomorphic SetIvar v6, :@a, v10 CheckInterrupts Return v10 @@ -3618,6 +3625,7 @@ mod hir_opt_tests { bb2(v6:BasicObject): v10:Fixnum[5] = Const Value(5) PatchPoint SingleRactorMode + IncrCounter setivar_fallback_shape_transition SetIvar v6, :@a, v10 CheckInterrupts Return v10 @@ -5527,6 +5535,7 @@ mod hir_opt_tests { v16:Fixnum[5] = Const Value(5) PatchPoint MethodRedefined(C@0x1000, foo=@0x1008, cme:0x1010) v26:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] + IncrCounter setivar_fallback_shape_transition SetIvar v26, :@foo, v16 CheckInterrupts Return v16 @@ -5558,6 +5567,7 @@ mod hir_opt_tests { v16:Fixnum[5] = Const Value(5) PatchPoint MethodRedefined(C@0x1000, foo=@0x1008, cme:0x1010) v26:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] + IncrCounter setivar_fallback_shape_transition SetIvar v26, :@foo, v16 CheckInterrupts Return v16 @@ -8609,17 +8619,18 @@ mod hir_opt_tests { SetLocal l0, EP@3, v27 v39:BasicObject = GetLocal l0, EP@3 PatchPoint SingleRactorMode + IncrCounter setivar_fallback_shape_transition SetIvar v14, :@formatted, v39 v45:Class[VMFrozenCore] = Const Value(VALUE(0x1000)) PatchPoint MethodRedefined(Class@0x1008, lambda@0x1010, cme:0x1018) PatchPoint NoSingletonClass(Class@0x1008) - v59:BasicObject = CCallWithFrame RubyVM::FrozenCore.lambda@0x1040, v45, block=0x1048 + v60:BasicObject = CCallWithFrame RubyVM::FrozenCore.lambda@0x1040, v45, block=0x1048 v48:BasicObject = GetLocal l0, EP@6 v49:BasicObject = GetLocal l0, EP@5 v50:BasicObject = GetLocal l0, EP@4 v51:BasicObject = GetLocal l0, EP@3 CheckInterrupts - Return v59 + Return v60 "); } } diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index 1277db5b7e..890d92bc56 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -24,6 +24,15 @@ macro_rules! make_counters { optimized_send { $($optimized_send_counter_name:ident,)+ } + dynamic_setivar { + $($dynamic_setivar_counter_name:ident,)+ + } + dynamic_getivar { + $($dynamic_getivar_counter_name:ident,)+ + } + dynamic_definedivar { + $($dynamic_definedivar_counter_name:ident,)+ + } $($counter_name:ident,)+ ) => { /// Struct containing the counter values @@ -33,6 +42,9 @@ macro_rules! make_counters { $(pub $exit_counter_name: u64,)+ $(pub $dynamic_send_counter_name: u64,)+ $(pub $optimized_send_counter_name: u64,)+ + $(pub $dynamic_setivar_counter_name: u64,)+ + $(pub $dynamic_getivar_counter_name: u64,)+ + $(pub $dynamic_definedivar_counter_name: u64,)+ $(pub $counter_name: u64,)+ } @@ -44,6 +56,9 @@ macro_rules! make_counters { $($exit_counter_name,)+ $($dynamic_send_counter_name,)+ $($optimized_send_counter_name,)+ + $($dynamic_setivar_counter_name,)+ + $($dynamic_getivar_counter_name,)+ + $($dynamic_definedivar_counter_name,)+ $($counter_name,)+ } @@ -54,6 +69,9 @@ macro_rules! make_counters { $( Counter::$exit_counter_name => stringify!($exit_counter_name), )+ $( Counter::$dynamic_send_counter_name => stringify!($dynamic_send_counter_name), )+ $( Counter::$optimized_send_counter_name => stringify!($optimized_send_counter_name), )+ + $( Counter::$dynamic_setivar_counter_name => stringify!($dynamic_setivar_counter_name), )+ + $( Counter::$dynamic_getivar_counter_name => stringify!($dynamic_getivar_counter_name), )+ + $( Counter::$dynamic_definedivar_counter_name => stringify!($dynamic_definedivar_counter_name), )+ $( Counter::$counter_name => stringify!($counter_name), )+ } } @@ -64,6 +82,9 @@ macro_rules! make_counters { $( stringify!($exit_counter_name) => Some(Counter::$exit_counter_name), )+ $( stringify!($dynamic_send_counter_name) => Some(Counter::$dynamic_send_counter_name), )+ $( stringify!($optimized_send_counter_name) => Some(Counter::$optimized_send_counter_name), )+ + $( stringify!($dynamic_setivar_counter_name) => Some(Counter::$dynamic_setivar_counter_name), )+ + $( stringify!($dynamic_getivar_counter_name) => Some(Counter::$dynamic_getivar_counter_name), )+ + $( stringify!($dynamic_definedivar_counter_name) => Some(Counter::$dynamic_definedivar_counter_name), )+ $( stringify!($counter_name) => Some(Counter::$counter_name), )+ _ => None, } @@ -77,6 +98,9 @@ macro_rules! make_counters { $( Counter::$default_counter_name => std::ptr::addr_of_mut!(counters.$default_counter_name), )+ $( Counter::$exit_counter_name => std::ptr::addr_of_mut!(counters.$exit_counter_name), )+ $( Counter::$dynamic_send_counter_name => std::ptr::addr_of_mut!(counters.$dynamic_send_counter_name), )+ + $( Counter::$dynamic_setivar_counter_name => std::ptr::addr_of_mut!(counters.$dynamic_setivar_counter_name), )+ + $( Counter::$dynamic_getivar_counter_name => std::ptr::addr_of_mut!(counters.$dynamic_getivar_counter_name), )+ + $( Counter::$dynamic_definedivar_counter_name => std::ptr::addr_of_mut!(counters.$dynamic_definedivar_counter_name), )+ $( Counter::$optimized_send_counter_name => std::ptr::addr_of_mut!(counters.$optimized_send_counter_name), )+ $( Counter::$counter_name => std::ptr::addr_of_mut!(counters.$counter_name), )+ } @@ -103,6 +127,21 @@ macro_rules! make_counters { $( Counter::$optimized_send_counter_name, )+ ]; + /// List of other counters that are summed as dynamic_setivar_count. + pub const DYNAMIC_SETIVAR_COUNTERS: &'static [Counter] = &[ + $( Counter::$dynamic_setivar_counter_name, )+ + ]; + + /// List of other counters that are summed as dynamic_getivar_count. + pub const DYNAMIC_GETIVAR_COUNTERS: &'static [Counter] = &[ + $( Counter::$dynamic_getivar_counter_name, )+ + ]; + + /// List of other counters that are summed as dynamic_definedivar_count. + pub const DYNAMIC_DEFINEDIVAR_COUNTERS: &'static [Counter] = &[ + $( Counter::$dynamic_definedivar_counter_name, )+ + ]; + /// List of other counters that are available only for --zjit-stats. pub const OTHER_COUNTERS: &'static [Counter] = &[ $( Counter::$counter_name, )+ @@ -207,6 +246,35 @@ make_counters! { variadic_cfunc_optimized_send_count, } + // Ivar fallback counters that are summed as dynamic_setivar_count + dynamic_setivar { + // setivar_fallback_: Fallback reasons for dynamic setivar instructions + setivar_fallback_not_monomorphic, + setivar_fallback_immediate, + setivar_fallback_not_t_object, + setivar_fallback_too_complex, + setivar_fallback_frozen, + setivar_fallback_shape_transition, + } + + // Ivar fallback counters that are summed as dynamic_getivar_count + dynamic_getivar { + // getivar_fallback_: Fallback reasons for dynamic getivar instructions + getivar_fallback_not_monomorphic, + getivar_fallback_immediate, + getivar_fallback_not_t_object, + getivar_fallback_too_complex, + } + + // Ivar fallback counters that are summed as dynamic_definedivar_count + dynamic_definedivar { + // definedivar_fallback_: Fallback reasons for dynamic definedivar instructions + definedivar_fallback_not_monomorphic, + definedivar_fallback_immediate, + definedivar_fallback_not_t_object, + definedivar_fallback_too_complex, + } + // compile_error_: Compile error reasons compile_error_iseq_stack_too_large, compile_error_exception_handler, @@ -236,10 +304,6 @@ make_counters! { // The number of times YARV instructions are executed on JIT code zjit_insn_count, - // The number of times we do a dynamic ivar lookup from JIT code - dynamic_getivar_count, - dynamic_setivar_count, - // Method call def_type related to send without block fallback to dynamic dispatch unspecialized_send_without_block_def_type_iseq, unspecialized_send_without_block_def_type_cfunc, @@ -657,6 +721,33 @@ pub extern "C" fn rb_zjit_stats(_ec: EcPtr, _self: VALUE, target_key: VALUE) -> set_stat_usize!(hash, "optimized_send_count", optimized_send_count); set_stat_usize!(hash, "send_count", dynamic_send_count + optimized_send_count); + // Set send fallback counters for each setivar fallback reason + let mut dynamic_setivar_count = 0; + for &counter in DYNAMIC_SETIVAR_COUNTERS { + let count = unsafe { *counter_ptr(counter) }; + dynamic_setivar_count += count; + set_stat_usize!(hash, &counter.name(), count); + } + set_stat_usize!(hash, "dynamic_setivar_count", dynamic_setivar_count); + + // Set send fallback counters for each getivar fallback reason + let mut dynamic_getivar_count = 0; + for &counter in DYNAMIC_GETIVAR_COUNTERS { + let count = unsafe { *counter_ptr(counter) }; + dynamic_getivar_count += count; + set_stat_usize!(hash, &counter.name(), count); + } + set_stat_usize!(hash, "dynamic_getivar_count", dynamic_getivar_count); + + // Set send fallback counters for each definedivar fallback reason + let mut dynamic_definedivar_count = 0; + for &counter in DYNAMIC_DEFINEDIVAR_COUNTERS { + let count = unsafe { *counter_ptr(counter) }; + dynamic_definedivar_count += count; + set_stat_usize!(hash, &counter.name(), count); + } + set_stat_usize!(hash, "dynamic_definedivar_count", dynamic_definedivar_count); + // Set send fallback counters for Uncategorized let send_fallback_counters = ZJITState::get_send_fallback_counters(); for (op_idx, count) in send_fallback_counters.iter().enumerate().take(VM_INSTRUCTION_SIZE as usize) { |
