module ZJIT
This module allows for introspection of ZJIT, CRuby’s just-in-time compiler. Everything in the module is highly implementation specific and the API might be less stable compared to the standard library.
This module may not exist if ZJIT does not support the particular platform for which CRuby is built.
Public Class Methods
Enable ZJIT compilation.
# File zjit.rb, line 28 def enable return false if enabled? if Primitive.cexpr! 'RBOOL(rb_yjit_enabled_p)' warn("Only one JIT can be enabled at the same time.") return false end Primitive.rb_zjit_enable end
Check if ZJIT is enabled
# File zjit.rb, line 23 def enabled? Primitive.cexpr! 'RBOOL(rb_zjit_enabled_p)' end
A directive for the compiler to emit a breakpoint instruction at the call site of this method. To show this to ZJIT, say ‘::RubyVM::ZJIT.induce_breakpoint!` verbatim. Other forms are too dynamic to detect during compilation.
# File zjit.rb, line 61 def induce_breakpoint! = nil # Check if `--zjit-stats` is used def stats_enabled? Primitive.rb_zjit_stats_enabled_p end # Return ZJIT statistics as a Hash def stats(target_key = nil) Primitive.rb_zjit_stats(target_key) end # Discard statistics collected for `--zjit-stats`. def reset_stats! Primitive.rb_zjit_reset_stats_bang end # Get the summary of ZJIT statistics as a String def stats_string return unless stats = self.stats buf = +"***ZJIT: Printing ZJIT statistics on exit***\n" if stats[:guard_type_count]&.nonzero? stats[:guard_type_exit_ratio] = stats[:exit_guard_type_failure].to_f / stats[:guard_type_count] * 100 end if stats[:guard_shape_count]&.nonzero? stats[:guard_shape_exit_ratio] = stats[:exit_guard_shape_failure].to_f / stats[:guard_shape_count] * 100 end if stats[:code_region_bytes]&.nonzero? stats[:side_exit_size_ratio] = stats[:side_exit_size_bytes].to_f / stats[:code_region_bytes] * 100 end if stats[:compile_time_ns]&.nonzero? stats[:compile_side_exit_time_ratio] = stats[:compile_side_exit_time_ns].to_f / stats[:compile_time_ns] * 100 end # Show counters independent from exit_* or dynamic_send_* print_counters_with_prefix(prefix: 'not_inlined_cfuncs_', prompt: 'not inlined C methods', buf:, stats:, limit: 20) print_counters_with_prefix(prefix: 'ccall_', prompt: 'calls to C functions from JIT code', buf:, stats:, limit: 20) print_counters_with_prefix(prefix: 'iseq_calls_count_', prompt: 'most called JIT functions', buf:, stats:, limit: 20) # Don't show not_annotated_cfuncs right now because it mostly duplicates not_inlined_cfuncs # print_counters_with_prefix(prefix: 'not_annotated_cfuncs_', prompt: 'not annotated C methods', buf:, stats:, limit: 20) # Show fallback counters, ordered by the typical amount of fallbacks for the prefix at the time print_counters_with_prefix(prefix: 'unspecialized_send_def_type_', prompt: 'not optimized method types for send', buf:, stats:, limit: 20) print_counters_with_prefix(prefix: 'unspecialized_super_def_type_', prompt: 'not optimized method types for super', 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: 'send_direct_fallback_context_', prompt: 'SendDirect fallback contexts', buf:, stats:, limit: 10) 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) print_counters_with_prefix(prefix: 'inline_reject_', prompt: 'HIR-level inlining rejection reasons', buf:, stats:, limit: 10) # Show most popular unsupported call features. Because each call can # use multiple complex features, a decrease in this number does not # necessarily mean an increase in number of optimized calls. print_counters_with_prefix(prefix: 'complex_arg_pass_', prompt: 'popular complex argument-parameter features not optimized', buf:, stats:, limit: 10) print_counters_with_prefix(prefix: 'caller_splat_profile_', prompt: 'caller splat length profiles', buf:, stats:, limit: 10) # Show exit counters, ordered by the typical amount of exits for the prefix at the time print_counters_with_prefix(prefix: 'compile_error_', prompt: 'compile error reasons', buf:, stats:, limit: 20) print_counters_with_prefix(prefix: 'unhandled_yarv_insn_', prompt: 'unhandled YARV insns', buf:, stats:, limit: 20) print_counters_with_prefix(prefix: 'unhandled_hir_insn_', prompt: 'unhandled HIR insns', buf:, stats:, limit: 20) print_counters_with_prefix(prefix: 'exit_', prompt: 'side exit reasons', buf:, stats:, limit: 20) # Show no-prefix counters, having the most important stat `ratio_in_zjit` at the end print_counters([ :send_count, :dynamic_send_count, :optimized_send_count, :iseq_optimized_send_count, :inline_cfunc_optimized_send_count, :inline_iseq_optimized_send_count, :inline_method_count, :empty_inline_frame_count, :non_variadic_cfunc_optimized_send_count, :variadic_cfunc_optimized_send_count, :caller_splat_optimized, ], buf:, stats:, right_align: true, base: :send_count) print_counters([ :dynamic_setivar_count, :dynamic_getivar_count, :dynamic_definedivar_count, :compiled_iseq_count, :compiled_side_exit_count, :failed_iseq_count, :compile_time_ns, :compile_side_exit_time_ns, :compile_side_exit_time_ratio, :compile_hir_time_ns, :compile_hir_build_time_ns, :compile_hir_strength_reduce_time_ns, :compile_hir_inline_methods_time_ns, :compile_hir_remove_trivial_block_params_time_ns, :compile_hir_optimize_load_store_time_ns, :compile_hir_canonicalize_time_ns, :compile_hir_fold_constants_time_ns, :compile_hir_clean_cfg_time_ns, :compile_hir_remove_redundant_patch_points_time_ns, :compile_hir_remove_duplicate_check_interrupts_time_ns, :compile_hir_eliminate_dead_code_time_ns, :compile_lir_time_ns, :profile_time_ns, :gc_time_ns, :invalidation_time_ns, :vm_write_jit_frame_count, :vm_write_sp_count, :vm_write_locals_count, :vm_write_stack_count, :vm_write_to_parent_iseq_local_count, :guard_type_count, :guard_type_exit_ratio, :guard_shape_count, :guard_shape_exit_ratio, :load_field_count, :store_field_count, :throw_count, :side_exit_size_bytes, :side_exit_size_ratio, :jit_frame_heap_bytes, :jit_frame_region_bytes, :code_region_bytes, :zjit_alloc_bytes, :total_mem_bytes, :total_native_stack_bytes, :side_exit_count, :total_insn_count, :vm_insn_count, :zjit_insn_count, :ratio_in_zjit, ], buf:, stats:) buf end # Assert that any future ZJIT compilation will return a function pointer def assert_compiles # :nodoc: Primitive.rb_zjit_assert_compiles end # :stopdoc: private # Register a block to be called when ZJIT is enabled def add_jit_hook(hook) @jit_hooks << hook end # Run ZJIT hooks registered by `#with_jit` def call_jit_hooks @jit_hooks.each(&:call) @jit_hooks.clear end def print_counters(keys, buf:, stats:, right_align: false, base: nil) key_pad = keys.map { |key| key.to_s.sub(/_time_ns\z/, '_time').size }.max + 1 key_align = '-' unless right_align value_pad = keys.filter_map { |key| stats[key] }.map { |value| number_with_delimiter(value).size }.max keys.each do |key| # Some stats like vm_insn_count and ratio_in_zjit are not supported on the release build next unless stats.key?(key) value = stats[key] if base && key != base total = stats[base] if total.nonzero? ratio = " (%4.1f%%)" % (100.0 * value / total) end end case key when :ratio_in_zjit value = '%0.1f%%' % value when :guard_type_exit_ratio, :guard_shape_exit_ratio, :side_exit_size_ratio, :compile_side_exit_time_ratio value = '%0.1f%%' % value when /_time_ns\z/ key = key.to_s.sub(/_time_ns\z/, '_time') value = "#{number_with_delimiter(value / 10**6)}ms" else value = number_with_delimiter(value) end buf << "%#{key_align}*s %*s%s\n" % [key_pad, "#{key}:", value_pad, value, ratio] end end def print_counters_with_prefix(buf:, stats:, prefix:, prompt:, limit: nil) counters = stats.select { |key, value| key.start_with?(prefix) && value > 0 } return if counters.empty? counters.transform_keys! { |key| key.to_s.delete_prefix(prefix) } total = counters.values.sum counters = counters.to_a counters.sort_by! { |_, value| -value } counters = counters.first(limit) if limit key_pad = counters.map { |key, _| key.size }.max value_pad = counters.map { |_, value| number_with_delimiter(value).size }.max buf << "Top-#{counters.size} " if limit buf << "#{prompt}" buf << " (%.1f%% of total #{number_with_delimiter(total)})" % (100.0 * counters.map(&:last).sum / total) if limit buf << ":\n" counters.each do |key, value| buf << " %*s: %*s (%4.1f%%)\n" % [key_pad, key, value_pad, number_with_delimiter(value), (100.0 * value / total)] end end def number_with_delimiter(number) s = number.to_s i = s.index('.') || s.size s.insert(i -= 3, ',') while i > 3 s end # Print ZJIT stats def print_stats $stderr.write stats_string end # Print ZJIT stats to file def print_stats_file filename = Primitive.rb_zjit_get_stats_file_path_p File.open(filename, "wb") do |file| if filename.end_with?(".json") require "json" file.write(JSON.pretty_generate(stats)) else file.write stats_string end end end end
A directive for the compiler to fail to compile the call to this method. To show this to ZJIT, say ‘::RubyVM::ZJIT.induce_compile_failure!` verbatim. Other forms are too dynamic to detect during compilation.
Actually running this method does nothing, whether ZJIT sees the call or not.
# File zjit.rb, line 49 def induce_compile_failure! = nil # A directive for the compiler to exit out of compiled code at the call site of this method. # To show this to ZJIT, say `::RubyVM::ZJIT.induce_side_exit!` verbatim. # Other forms are too dynamic to detect during compilation. # # Actually running this method does nothing, whether ZJIT sees the call or not. def induce_side_exit! = nil # A directive for the compiler to emit a breakpoint instruction at the call site of this method. # To show this to ZJIT, say `::RubyVM::ZJIT.induce_breakpoint!` verbatim. # Other forms are too dynamic to detect during compilation. def induce_breakpoint! = nil # Check if `--zjit-stats` is used def stats_enabled? Primitive.rb_zjit_stats_enabled_p end # Return ZJIT statistics as a Hash def stats(target_key = nil) Primitive.rb_zjit_stats(target_key) end # Discard statistics collected for `--zjit-stats`. def reset_stats! Primitive.rb_zjit_reset_stats_bang end # Get the summary of ZJIT statistics as a String def stats_string return unless stats = self.stats buf = +"***ZJIT: Printing ZJIT statistics on exit***\n" if stats[:guard_type_count]&.nonzero? stats[:guard_type_exit_ratio] = stats[:exit_guard_type_failure].to_f / stats[:guard_type_count] * 100 end if stats[:guard_shape_count]&.nonzero? stats[:guard_shape_exit_ratio] = stats[:exit_guard_shape_failure].to_f / stats[:guard_shape_count] * 100 end if stats[:code_region_bytes]&.nonzero? stats[:side_exit_size_ratio] = stats[:side_exit_size_bytes].to_f / stats[:code_region_bytes] * 100 end if stats[:compile_time_ns]&.nonzero? stats[:compile_side_exit_time_ratio] = stats[:compile_side_exit_time_ns].to_f / stats[:compile_time_ns] * 100 end # Show counters independent from exit_* or dynamic_send_* print_counters_with_prefix(prefix: 'not_inlined_cfuncs_', prompt: 'not inlined C methods', buf:, stats:, limit: 20) print_counters_with_prefix(prefix: 'ccall_', prompt: 'calls to C functions from JIT code', buf:, stats:, limit: 20) print_counters_with_prefix(prefix: 'iseq_calls_count_', prompt: 'most called JIT functions', buf:, stats:, limit: 20) # Don't show not_annotated_cfuncs right now because it mostly duplicates not_inlined_cfuncs # print_counters_with_prefix(prefix: 'not_annotated_cfuncs_', prompt: 'not annotated C methods', buf:, stats:, limit: 20) # Show fallback counters, ordered by the typical amount of fallbacks for the prefix at the time print_counters_with_prefix(prefix: 'unspecialized_send_def_type_', prompt: 'not optimized method types for send', buf:, stats:, limit: 20) print_counters_with_prefix(prefix: 'unspecialized_super_def_type_', prompt: 'not optimized method types for super', 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: 'send_direct_fallback_context_', prompt: 'SendDirect fallback contexts', buf:, stats:, limit: 10) 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) print_counters_with_prefix(prefix: 'inline_reject_', prompt: 'HIR-level inlining rejection reasons', buf:, stats:, limit: 10) # Show most popular unsupported call features. Because each call can # use multiple complex features, a decrease in this number does not # necessarily mean an increase in number of optimized calls. print_counters_with_prefix(prefix: 'complex_arg_pass_', prompt: 'popular complex argument-parameter features not optimized', buf:, stats:, limit: 10) print_counters_with_prefix(prefix: 'caller_splat_profile_', prompt: 'caller splat length profiles', buf:, stats:, limit: 10) # Show exit counters, ordered by the typical amount of exits for the prefix at the time print_counters_with_prefix(prefix: 'compile_error_', prompt: 'compile error reasons', buf:, stats:, limit: 20) print_counters_with_prefix(prefix: 'unhandled_yarv_insn_', prompt: 'unhandled YARV insns', buf:, stats:, limit: 20) print_counters_with_prefix(prefix: 'unhandled_hir_insn_', prompt: 'unhandled HIR insns', buf:, stats:, limit: 20) print_counters_with_prefix(prefix: 'exit_', prompt: 'side exit reasons', buf:, stats:, limit: 20) # Show no-prefix counters, having the most important stat `ratio_in_zjit` at the end print_counters([ :send_count, :dynamic_send_count, :optimized_send_count, :iseq_optimized_send_count, :inline_cfunc_optimized_send_count, :inline_iseq_optimized_send_count, :inline_method_count, :empty_inline_frame_count, :non_variadic_cfunc_optimized_send_count, :variadic_cfunc_optimized_send_count, :caller_splat_optimized, ], buf:, stats:, right_align: true, base: :send_count) print_counters([ :dynamic_setivar_count, :dynamic_getivar_count, :dynamic_definedivar_count, :compiled_iseq_count, :compiled_side_exit_count, :failed_iseq_count, :compile_time_ns, :compile_side_exit_time_ns, :compile_side_exit_time_ratio, :compile_hir_time_ns, :compile_hir_build_time_ns, :compile_hir_strength_reduce_time_ns, :compile_hir_inline_methods_time_ns, :compile_hir_remove_trivial_block_params_time_ns, :compile_hir_optimize_load_store_time_ns, :compile_hir_canonicalize_time_ns, :compile_hir_fold_constants_time_ns, :compile_hir_clean_cfg_time_ns, :compile_hir_remove_redundant_patch_points_time_ns, :compile_hir_remove_duplicate_check_interrupts_time_ns, :compile_hir_eliminate_dead_code_time_ns, :compile_lir_time_ns, :profile_time_ns, :gc_time_ns, :invalidation_time_ns, :vm_write_jit_frame_count, :vm_write_sp_count, :vm_write_locals_count, :vm_write_stack_count, :vm_write_to_parent_iseq_local_count, :guard_type_count, :guard_type_exit_ratio, :guard_shape_count, :guard_shape_exit_ratio, :load_field_count, :store_field_count, :throw_count, :side_exit_size_bytes, :side_exit_size_ratio, :jit_frame_heap_bytes, :jit_frame_region_bytes, :code_region_bytes, :zjit_alloc_bytes, :total_mem_bytes, :total_native_stack_bytes, :side_exit_count, :total_insn_count, :vm_insn_count, :zjit_insn_count, :ratio_in_zjit, ], buf:, stats:) buf end # Assert that any future ZJIT compilation will return a function pointer def assert_compiles # :nodoc: Primitive.rb_zjit_assert_compiles end # :stopdoc: private # Register a block to be called when ZJIT is enabled def add_jit_hook(hook) @jit_hooks << hook end # Run ZJIT hooks registered by `#with_jit` def call_jit_hooks @jit_hooks.each(&:call) @jit_hooks.clear end def print_counters(keys, buf:, stats:, right_align: false, base: nil) key_pad = keys.map { |key| key.to_s.sub(/_time_ns\z/, '_time').size }.max + 1 key_align = '-' unless right_align value_pad = keys.filter_map { |key| stats[key] }.map { |value| number_with_delimiter(value).size }.max keys.each do |key| # Some stats like vm_insn_count and ratio_in_zjit are not supported on the release build next unless stats.key?(key) value = stats[key] if base && key != base total = stats[base] if total.nonzero? ratio = " (%4.1f%%)" % (100.0 * value / total) end end case key when :ratio_in_zjit value = '%0.1f%%' % value when :guard_type_exit_ratio, :guard_shape_exit_ratio, :side_exit_size_ratio, :compile_side_exit_time_ratio value = '%0.1f%%' % value when /_time_ns\z/ key = key.to_s.sub(/_time_ns\z/, '_time') value = "#{number_with_delimiter(value / 10**6)}ms" else value = number_with_delimiter(value) end buf << "%#{key_align}*s %*s%s\n" % [key_pad, "#{key}:", value_pad, value, ratio] end end def print_counters_with_prefix(buf:, stats:, prefix:, prompt:, limit: nil) counters = stats.select { |key, value| key.start_with?(prefix) && value > 0 } return if counters.empty? counters.transform_keys! { |key| key.to_s.delete_prefix(prefix) } total = counters.values.sum counters = counters.to_a counters.sort_by! { |_, value| -value } counters = counters.first(limit) if limit key_pad = counters.map { |key, _| key.size }.max value_pad = counters.map { |_, value| number_with_delimiter(value).size }.max buf << "Top-#{counters.size} " if limit buf << "#{prompt}" buf << " (%.1f%% of total #{number_with_delimiter(total)})" % (100.0 * counters.map(&:last).sum / total) if limit buf << ":\n" counters.each do |key, value| buf << " %*s: %*s (%4.1f%%)\n" % [key_pad, key, value_pad, number_with_delimiter(value), (100.0 * value / total)] end end def number_with_delimiter(number) s = number.to_s i = s.index('.') || s.size s.insert(i -= 3, ',') while i > 3 s end # Print ZJIT stats def print_stats $stderr.write stats_string end # Print ZJIT stats to file def print_stats_file filename = Primitive.rb_zjit_get_stats_file_path_p File.open(filename, "wb") do |file| if filename.end_with?(".json") require "json" file.write(JSON.pretty_generate(stats)) else file.write stats_string end end
A directive for the compiler to exit out of compiled code at the call site of this method. To show this to ZJIT, say ‘::RubyVM::ZJIT.induce_side_exit!` verbatim. Other forms are too dynamic to detect during compilation.
Actually running this method does nothing, whether ZJIT sees the call or not.
# File zjit.rb, line 56 def induce_side_exit! = nil # A directive for the compiler to emit a breakpoint instruction at the call site of this method. # To show this to ZJIT, say `::RubyVM::ZJIT.induce_breakpoint!` verbatim. # Other forms are too dynamic to detect during compilation. def induce_breakpoint! = nil # Check if `--zjit-stats` is used def stats_enabled? Primitive.rb_zjit_stats_enabled_p end # Return ZJIT statistics as a Hash def stats(target_key = nil) Primitive.rb_zjit_stats(target_key) end # Discard statistics collected for `--zjit-stats`. def reset_stats! Primitive.rb_zjit_reset_stats_bang end # Get the summary of ZJIT statistics as a String def stats_string return unless stats = self.stats buf = +"***ZJIT: Printing ZJIT statistics on exit***\n" if stats[:guard_type_count]&.nonzero? stats[:guard_type_exit_ratio] = stats[:exit_guard_type_failure].to_f / stats[:guard_type_count] * 100 end if stats[:guard_shape_count]&.nonzero? stats[:guard_shape_exit_ratio] = stats[:exit_guard_shape_failure].to_f / stats[:guard_shape_count] * 100 end if stats[:code_region_bytes]&.nonzero? stats[:side_exit_size_ratio] = stats[:side_exit_size_bytes].to_f / stats[:code_region_bytes] * 100 end if stats[:compile_time_ns]&.nonzero? stats[:compile_side_exit_time_ratio] = stats[:compile_side_exit_time_ns].to_f / stats[:compile_time_ns] * 100 end # Show counters independent from exit_* or dynamic_send_* print_counters_with_prefix(prefix: 'not_inlined_cfuncs_', prompt: 'not inlined C methods', buf:, stats:, limit: 20) print_counters_with_prefix(prefix: 'ccall_', prompt: 'calls to C functions from JIT code', buf:, stats:, limit: 20) print_counters_with_prefix(prefix: 'iseq_calls_count_', prompt: 'most called JIT functions', buf:, stats:, limit: 20) # Don't show not_annotated_cfuncs right now because it mostly duplicates not_inlined_cfuncs # print_counters_with_prefix(prefix: 'not_annotated_cfuncs_', prompt: 'not annotated C methods', buf:, stats:, limit: 20) # Show fallback counters, ordered by the typical amount of fallbacks for the prefix at the time print_counters_with_prefix(prefix: 'unspecialized_send_def_type_', prompt: 'not optimized method types for send', buf:, stats:, limit: 20) print_counters_with_prefix(prefix: 'unspecialized_super_def_type_', prompt: 'not optimized method types for super', 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: 'send_direct_fallback_context_', prompt: 'SendDirect fallback contexts', buf:, stats:, limit: 10) 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) print_counters_with_prefix(prefix: 'inline_reject_', prompt: 'HIR-level inlining rejection reasons', buf:, stats:, limit: 10) # Show most popular unsupported call features. Because each call can # use multiple complex features, a decrease in this number does not # necessarily mean an increase in number of optimized calls. print_counters_with_prefix(prefix: 'complex_arg_pass_', prompt: 'popular complex argument-parameter features not optimized', buf:, stats:, limit: 10) print_counters_with_prefix(prefix: 'caller_splat_profile_', prompt: 'caller splat length profiles', buf:, stats:, limit: 10) # Show exit counters, ordered by the typical amount of exits for the prefix at the time print_counters_with_prefix(prefix: 'compile_error_', prompt: 'compile error reasons', buf:, stats:, limit: 20) print_counters_with_prefix(prefix: 'unhandled_yarv_insn_', prompt: 'unhandled YARV insns', buf:, stats:, limit: 20) print_counters_with_prefix(prefix: 'unhandled_hir_insn_', prompt: 'unhandled HIR insns', buf:, stats:, limit: 20) print_counters_with_prefix(prefix: 'exit_', prompt: 'side exit reasons', buf:, stats:, limit: 20) # Show no-prefix counters, having the most important stat `ratio_in_zjit` at the end print_counters([ :send_count, :dynamic_send_count, :optimized_send_count, :iseq_optimized_send_count, :inline_cfunc_optimized_send_count, :inline_iseq_optimized_send_count, :inline_method_count, :empty_inline_frame_count, :non_variadic_cfunc_optimized_send_count, :variadic_cfunc_optimized_send_count, :caller_splat_optimized, ], buf:, stats:, right_align: true, base: :send_count) print_counters([ :dynamic_setivar_count, :dynamic_getivar_count, :dynamic_definedivar_count, :compiled_iseq_count, :compiled_side_exit_count, :failed_iseq_count, :compile_time_ns, :compile_side_exit_time_ns, :compile_side_exit_time_ratio, :compile_hir_time_ns, :compile_hir_build_time_ns, :compile_hir_strength_reduce_time_ns, :compile_hir_inline_methods_time_ns, :compile_hir_remove_trivial_block_params_time_ns, :compile_hir_optimize_load_store_time_ns, :compile_hir_canonicalize_time_ns, :compile_hir_fold_constants_time_ns, :compile_hir_clean_cfg_time_ns, :compile_hir_remove_redundant_patch_points_time_ns, :compile_hir_remove_duplicate_check_interrupts_time_ns, :compile_hir_eliminate_dead_code_time_ns, :compile_lir_time_ns, :profile_time_ns, :gc_time_ns, :invalidation_time_ns, :vm_write_jit_frame_count, :vm_write_sp_count, :vm_write_locals_count, :vm_write_stack_count, :vm_write_to_parent_iseq_local_count, :guard_type_count, :guard_type_exit_ratio, :guard_shape_count, :guard_shape_exit_ratio, :load_field_count, :store_field_count, :throw_count, :side_exit_size_bytes, :side_exit_size_ratio, :jit_frame_heap_bytes, :jit_frame_region_bytes, :code_region_bytes, :zjit_alloc_bytes, :total_mem_bytes, :total_native_stack_bytes, :side_exit_count, :total_insn_count, :vm_insn_count, :zjit_insn_count, :ratio_in_zjit, ], buf:, stats:) buf end # Assert that any future ZJIT compilation will return a function pointer def assert_compiles # :nodoc: Primitive.rb_zjit_assert_compiles end # :stopdoc: private # Register a block to be called when ZJIT is enabled def add_jit_hook(hook) @jit_hooks << hook end # Run ZJIT hooks registered by `#with_jit` def call_jit_hooks @jit_hooks.each(&:call) @jit_hooks.clear end def print_counters(keys, buf:, stats:, right_align: false, base: nil) key_pad = keys.map { |key| key.to_s.sub(/_time_ns\z/, '_time').size }.max + 1 key_align = '-' unless right_align value_pad = keys.filter_map { |key| stats[key] }.map { |value| number_with_delimiter(value).size }.max keys.each do |key| # Some stats like vm_insn_count and ratio_in_zjit are not supported on the release build next unless stats.key?(key) value = stats[key] if base && key != base total = stats[base] if total.nonzero? ratio = " (%4.1f%%)" % (100.0 * value / total) end end case key when :ratio_in_zjit value = '%0.1f%%' % value when :guard_type_exit_ratio, :guard_shape_exit_ratio, :side_exit_size_ratio, :compile_side_exit_time_ratio value = '%0.1f%%' % value when /_time_ns\z/ key = key.to_s.sub(/_time_ns\z/, '_time') value = "#{number_with_delimiter(value / 10**6)}ms" else value = number_with_delimiter(value) end buf << "%#{key_align}*s %*s%s\n" % [key_pad, "#{key}:", value_pad, value, ratio] end end def print_counters_with_prefix(buf:, stats:, prefix:, prompt:, limit: nil) counters = stats.select { |key, value| key.start_with?(prefix) && value > 0 } return if counters.empty? counters.transform_keys! { |key| key.to_s.delete_prefix(prefix) } total = counters.values.sum counters = counters.to_a counters.sort_by! { |_, value| -value } counters = counters.first(limit) if limit key_pad = counters.map { |key, _| key.size }.max value_pad = counters.map { |_, value| number_with_delimiter(value).size }.max buf << "Top-#{counters.size} " if limit buf << "#{prompt}" buf << " (%.1f%% of total #{number_with_delimiter(total)})" % (100.0 * counters.map(&:last).sum / total) if limit buf << ":\n" counters.each do |key, value| buf << " %*s: %*s (%4.1f%%)\n" % [key_pad, key, value_pad, number_with_delimiter(value), (100.0 * value / total)] end end def number_with_delimiter(number) s = number.to_s i = s.index('.') || s.size s.insert(i -= 3, ',') while i > 3 s end # Print ZJIT stats def print_stats $stderr.write stats_string end # Print ZJIT stats to file def print_stats_file filename = Primitive.rb_zjit_get_stats_file_path_p File.open(filename, "wb") do |file| if filename.end_with?(".json") require "json" file.write(JSON.pretty_generate(stats)) else file.write stats_string end end end
Discard statistics collected for ‘–zjit-stats`.
# File zjit.rb, line 74 def reset_stats! Primitive.rb_zjit_reset_stats_bang end
Check if ‘–zjit-stats` is used
# File zjit.rb, line 64 def stats_enabled? Primitive.rb_zjit_stats_enabled_p end
Get the summary of ZJIT statistics as a String
# File zjit.rb, line 79 def stats_string return unless stats = self.stats buf = +"***ZJIT: Printing ZJIT statistics on exit***\n" if stats[:guard_type_count]&.nonzero? stats[:guard_type_exit_ratio] = stats[:exit_guard_type_failure].to_f / stats[:guard_type_count] * 100 end if stats[:guard_shape_count]&.nonzero? stats[:guard_shape_exit_ratio] = stats[:exit_guard_shape_failure].to_f / stats[:guard_shape_count] * 100 end if stats[:code_region_bytes]&.nonzero? stats[:side_exit_size_ratio] = stats[:side_exit_size_bytes].to_f / stats[:code_region_bytes] * 100 end if stats[:compile_time_ns]&.nonzero? stats[:compile_side_exit_time_ratio] = stats[:compile_side_exit_time_ns].to_f / stats[:compile_time_ns] * 100 end # Show counters independent from exit_* or dynamic_send_* print_counters_with_prefix(prefix: 'not_inlined_cfuncs_', prompt: 'not inlined C methods', buf:, stats:, limit: 20) print_counters_with_prefix(prefix: 'ccall_', prompt: 'calls to C functions from JIT code', buf:, stats:, limit: 20) print_counters_with_prefix(prefix: 'iseq_calls_count_', prompt: 'most called JIT functions', buf:, stats:, limit: 20) # Don't show not_annotated_cfuncs right now because it mostly duplicates not_inlined_cfuncs # print_counters_with_prefix(prefix: 'not_annotated_cfuncs_', prompt: 'not annotated C methods', buf:, stats:, limit: 20) # Show fallback counters, ordered by the typical amount of fallbacks for the prefix at the time print_counters_with_prefix(prefix: 'unspecialized_send_def_type_', prompt: 'not optimized method types for send', buf:, stats:, limit: 20) print_counters_with_prefix(prefix: 'unspecialized_super_def_type_', prompt: 'not optimized method types for super', 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: 'send_direct_fallback_context_', prompt: 'SendDirect fallback contexts', buf:, stats:, limit: 10) 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) print_counters_with_prefix(prefix: 'inline_reject_', prompt: 'HIR-level inlining rejection reasons', buf:, stats:, limit: 10) # Show most popular unsupported call features. Because each call can # use multiple complex features, a decrease in this number does not # necessarily mean an increase in number of optimized calls. print_counters_with_prefix(prefix: 'complex_arg_pass_', prompt: 'popular complex argument-parameter features not optimized', buf:, stats:, limit: 10) print_counters_with_prefix(prefix: 'caller_splat_profile_', prompt: 'caller splat length profiles', buf:, stats:, limit: 10) # Show exit counters, ordered by the typical amount of exits for the prefix at the time print_counters_with_prefix(prefix: 'compile_error_', prompt: 'compile error reasons', buf:, stats:, limit: 20) print_counters_with_prefix(prefix: 'unhandled_yarv_insn_', prompt: 'unhandled YARV insns', buf:, stats:, limit: 20) print_counters_with_prefix(prefix: 'unhandled_hir_insn_', prompt: 'unhandled HIR insns', buf:, stats:, limit: 20) print_counters_with_prefix(prefix: 'exit_', prompt: 'side exit reasons', buf:, stats:, limit: 20) # Show no-prefix counters, having the most important stat `ratio_in_zjit` at the end print_counters([ :send_count, :dynamic_send_count, :optimized_send_count, :iseq_optimized_send_count, :inline_cfunc_optimized_send_count, :inline_iseq_optimized_send_count, :inline_method_count, :empty_inline_frame_count, :non_variadic_cfunc_optimized_send_count, :variadic_cfunc_optimized_send_count, :caller_splat_optimized, ], buf:, stats:, right_align: true, base: :send_count) print_counters([ :dynamic_setivar_count, :dynamic_getivar_count, :dynamic_definedivar_count, :compiled_iseq_count, :compiled_side_exit_count, :failed_iseq_count, :compile_time_ns, :compile_side_exit_time_ns, :compile_side_exit_time_ratio, :compile_hir_time_ns, :compile_hir_build_time_ns, :compile_hir_strength_reduce_time_ns, :compile_hir_inline_methods_time_ns, :compile_hir_remove_trivial_block_params_time_ns, :compile_hir_optimize_load_store_time_ns, :compile_hir_canonicalize_time_ns, :compile_hir_fold_constants_time_ns, :compile_hir_clean_cfg_time_ns, :compile_hir_remove_redundant_patch_points_time_ns, :compile_hir_remove_duplicate_check_interrupts_time_ns, :compile_hir_eliminate_dead_code_time_ns, :compile_lir_time_ns, :profile_time_ns, :gc_time_ns, :invalidation_time_ns, :vm_write_jit_frame_count, :vm_write_sp_count, :vm_write_locals_count, :vm_write_stack_count, :vm_write_to_parent_iseq_local_count, :guard_type_count, :guard_type_exit_ratio, :guard_shape_count, :guard_shape_exit_ratio, :load_field_count, :store_field_count, :throw_count, :side_exit_size_bytes, :side_exit_size_ratio, :jit_frame_heap_bytes, :jit_frame_region_bytes, :code_region_bytes, :zjit_alloc_bytes, :total_mem_bytes, :total_native_stack_bytes, :side_exit_count, :total_insn_count, :vm_insn_count, :zjit_insn_count, :ratio_in_zjit, ], buf:, stats:) buf end
Check if ‘–zjit-trace-exits` is used
# File zjit.rb, line 40 def trace_exit_locations_enabled? Primitive.rb_zjit_trace_exit_locations_enabled_p end