Module: RubyVM::ZJIT
| Relationships & Source Files | |
| Defined in: | zjit.rb |
Overview
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.
Class Attribute Summary
-
.enabled? ⇒ Boolean
readonly
Check if
ZJITis enabled. -
.max_compile_time_ns
rw
The maximum amount of time
ZJITis allowed to spend compiling. - .max_compile_time_ns=(max_compile_time_ns) rw
-
.stats_enabled? ⇒ Boolean
readonly
Check if
--zjit-statsis used. -
.trace_exit_locations_enabled? ⇒ Boolean
readonly
Check if
--zjit-trace-exitsis used.
Class Method Summary
-
.enable
Enable ZJIT compilation.
-
.induce_breakpoint!
A directive for the compiler to emit a breakpoint instruction at the call site of this method.
-
.induce_compile_failure!
A directive for the compiler to fail to compile the call to this method.
-
.induce_side_exit!
A directive for the compiler to exit out of compiled code at the call site of this method.
-
.reset_stats!
Discard statistics collected for
--zjit-stats. -
.stats(target_key = nil)
Return ZJIT statistics as a
::Hash. -
.stats_string
Get the summary of
ZJITstatistics as a::String. -
.total_compile_time_ns
Monotonically increasing counter representing the amount of wall time nanoseconds spent compiling.
-
.assert_compiles
Internal use only
Assert that any future
ZJITcompilation will return a function pointer. -
.add_jit_hook(hook)
private
Internal use only
Register a block to be called when
ZJITis enabled. -
.call_jit_hooks
private
Internal use only
Run ZJIT hooks registered by
#with_jit - .number_with_delimiter(number) private Internal use only
- .print_counters(keys, buf:, stats:, right_align: false, base: nil) private Internal use only
- .print_counters_with_prefix(buf:, stats:, prefix:, prompt:, limit: nil) private Internal use only
-
.print_stats
private
Internal use only
Print ZJIT stats.
-
.print_stats_file
private
Internal use only
Print ZJIT stats to file.
Class Attribute Details
.enabled? ⇒ Boolean (readonly)
Check if ZJIT is enabled
# File 'zjit.rb', line 24
def enabled? Primitive.cexpr! 'RBOOL(rb_zjit_enabled_p)' end
.max_compile_time_ns (rw)
The maximum amount of time ZJIT is allowed to spend compiling.
When .total_compile_time_ns goes over max_compile_time_ns, YJIT
pause compilation until max_compile_time_ns is raised.
If set to 0, there is no limit.
This is a best-effort functionality, there are situations in which ZJIT may
still compile even when paused.
This feature is intended to be used to control how fast the JIT warms up,
to spread the compilation overhead over a longer period of time.
# File 'zjit.rb', line 78
def max_compile_time_ns Primitive.rb_zjit_max_compile_time_ns_get end
.max_compile_time_ns=(max_compile_time_ns) (rw)
[ GitHub ]# File 'zjit.rb', line 83
def max_compile_time_ns=(max_compile_time_ns) Primitive.rb_zjit_max_compile_time_ns_set(max_compile_time_ns) end
.stats_enabled? ⇒ Boolean (readonly)
Check if --zjit-stats is used
# File 'zjit.rb', line 88
def stats_enabled? Primitive.rb_zjit_stats_enabled_p end
.trace_exit_locations_enabled? ⇒ Boolean (readonly)
Check if --zjit-trace-exits is used
# File 'zjit.rb', line 41
def trace_exit_locations_enabled? Primitive.rb_zjit_trace_exit_locations_enabled_p end
Class Method Details
.add_jit_hook(hook) (private)
Register a block to be called when ZJIT is enabled
# File 'zjit.rb', line 238
def add_jit_hook(hook) @jit_hooks << hook end
.assert_compiles
Assert that any future ZJIT compilation will return a function pointer
# File 'zjit.rb', line 230
def assert_compiles # :nodoc: Primitive.rb_zjit_assert_compiles end
.call_jit_hooks (private)
Run ZJIT hooks registered by #with_jit
# File 'zjit.rb', line 243
def call_jit_hooks @jit_hooks.each(&:call) @jit_hooks.clear end
.enable
Enable ZJIT compilation.
# File 'zjit.rb', line 29
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
.induce_breakpoint!
A directive for the compiler to emit a breakpoint instruction at the call site of this method.
To show this to ZJIT, say .induce_breakpoint! verbatim.
Other forms are too dynamic to detect during compilation.
# File 'zjit.rb', line 62
def induce_breakpoint! = nil
.induce_compile_failure!
A directive for the compiler to fail to compile the call to this method.
To show this to ZJIT, say .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 50
def induce_compile_failure! = nil
.induce_side_exit!
A directive for the compiler to exit out of compiled code at the call site of this method.
To show this to ZJIT, say .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 57
def induce_side_exit! = nil
.number_with_delimiter(number) (private)
# File 'zjit.rb', line 303
def number_with_delimiter(number) s = number.to_s i = s.index('.') || s.size s.insert(i -= 3, ',') while i > 3 s end
.print_counters(keys, buf:, stats:, right_align: false, base: nil) (private)
# File 'zjit.rb', line 248
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
.print_counters_with_prefix(buf:, stats:, prefix:, prompt:, limit: nil) (private)
# File 'zjit.rb', line 280
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
.print_stats (private)
Print ZJIT stats
# File 'zjit.rb', line 311
def print_stats $stderr.write stats_string end
.print_stats_file (private)
Print ZJIT stats to file
# File 'zjit.rb', line 316
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
.reset_stats!
Discard statistics collected for --zjit-stats.
# File 'zjit.rb', line 98
def reset_stats! Primitive.rb_zjit_reset_stats_bang end
.stats(target_key = nil)
Return ZJIT statistics as a ::Hash
# File 'zjit.rb', line 93
def stats(target_key = nil) Primitive.rb_zjit_stats(target_key) end
.stats_string
Get the summary of ZJIT statistics as a ::String
# File 'zjit.rb', line 103
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
.total_compile_time_ns
Monotonically increasing counter representing the amount of wall time nanoseconds spent compiling.
# File 'zjit.rb', line 66
def total_compile_time_ns Primitive.rb_zjit_total_compile_time_ns_get end