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. -
.stats_enabled? ⇒ Boolean
readonly
Check if
–zjit-statsis used. -
.trace_exit_locations_enabled? ⇒ Boolean
readonly
Check if
–zjit-trace-exitsis used.
Class Method Summary
-
.dump_exit_locations(filename)
::Marshaldumps exit locations to the given filename. -
.exit_locations
If –zjit-trace-exits is enabled parse the hashes from
Primitive.rb_zjit_get_exit_locationsinto a format readable by Stackprof. -
.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. -
.assert_compiles
Internal use only
Assert that any future
ZJITcompilation will return a function pointer. - .dump_locations private Internal use only
- .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.
Class Attribute Details
.enabled? ⇒ Boolean (readonly)
Check if ZJIT is enabled
# File 'zjit.rb', line 21
def enabled? Primitive.cexpr! 'RBOOL(rb_zjit_enabled_p)' end
.stats_enabled? ⇒ Boolean (readonly)
Check if –zjit-stats is used
# File 'zjit.rb', line 135
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 26
def trace_exit_locations_enabled? Primitive.rb_zjit_trace_exit_locations_enabled_p end
Class Method Details
.assert_compiles
Assert that any future ZJIT compilation will return a function pointer
# File 'zjit.rb', line 217
def assert_compiles # :nodoc: Primitive.rb_zjit_assert_compiles end
.dump_exit_locations(filename)
::Marshal dumps exit locations to the given filename.
Usage:
In a script call:
RubyVM::ZJIT.dump_exit_locations("my_file.dump")
Then run the file with the following options:
ruby --zjit --zjit-stats --zjit-trace-exits test.rb
Once the code is done running, use Stackprof to read the dump file. See Stackprof documentation for options.
# File 'zjit.rb', line 123
def dump_exit_locations(filename) unless trace_exit_locations_enabled? raise ArgumentError, "--zjit-trace-exits must be enabled to use dump_exit_locations." end File.open(filename, "wb") do |file| Marshal.dump(RubyVM::ZJIT.exit_locations, file) file.size end end
.dump_locations (private)
# File 'zjit.rb', line 287
def dump_locations # :nodoc: return unless trace_exit_locations_enabled? filename = "zjit_exits_#{Process.pid}.dump" n_bytes = dump_exit_locations(filename) absolute_filename = File.(filename) $stderr.puts("#{n_bytes} bytes written to #{absolute_filename}") end
.exit_locations
If –zjit-trace-exits is enabled parse the hashes from Primitive.rb_zjit_get_exit_locations into a format readable by Stackprof. This will allow us to find the exact location of a side exit in ZJIT based on the instruction that is exiting.
# File 'zjit.rb', line 34
def exit_locations return unless trace_exit_locations_enabled? results = Primitive.rb_zjit_get_exit_locations raw_samples = results[:raw] line_samples = results[:lines] frames = results[:frames] samples_count = 0 # Use nonexistent.def as a dummy file name. frame_template = { samples: 0, total_samples: 0, edges: {}, name: name, file: "nonexistent.def", line: nil, lines: {} } # Loop through all possible instructions and setup the frame hash. RubyVM::INSTRUCTION_NAMES.each_with_index do |name, frame_id| frames[frame_id] = frame_template.dup.tap { |h| h[:name] = name } end # Loop through the raw_samples and build the hashes for StackProf. # The loop is based off an example in the StackProf documentation and therefore # this functionality can only work with that library. # # Raw Samples: # [ length, frame1, frame2, frameN, ..., instruction, count # # Line Samples # [ length, line_1, line_2, line_n, ..., dummy value, count i = 0 while i < raw_samples.length stack_length = raw_samples[i] i += 1 # consume the stack length sample_count = raw_samples[i + stack_length] prev_frame_id = nil stack_length.times do |idx| idx += i frame_id = raw_samples[idx] if prev_frame_id prev_frame = frames[prev_frame_id] prev_frame[:edges][frame_id] ||= 0 prev_frame[:edges][frame_id] += sample_count end frame_info = frames[frame_id] frame_info[:total_samples] += sample_count frame_info[:lines][line_samples[idx]] ||= [0, 0] frame_info[:lines][line_samples[idx]][0] += sample_count prev_frame_id = frame_id end i += stack_length # consume the stack top_frame_id = prev_frame_id top_frame_line = 1 frames[top_frame_id][:samples] += sample_count frames[top_frame_id][:lines] ||= {} frames[top_frame_id][:lines][top_frame_line] ||= [0, 0] frames[top_frame_id][:lines][top_frame_line][1] += sample_count samples_count += sample_count i += 1 end results[:samples] = samples_count # These values are mandatory to include for stackprof, but we don't use them. results[:missed_samples] = 0 results[:gc_samples] = 0 results end
.number_with_delimiter(number) (private)
# File 'zjit.rb', line 275
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 224
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 ratio = " (%4.1f%%)" % (100.0 * value / stats[base]) end case key when :ratio_in_zjit value = '%0.1f%%' % value when :guard_type_exit_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 253
def print_counters_with_prefix(buf:, stats:, prefix:, prompt:, limit: nil) counters = stats.select { |key, value| key.start_with?(prefix) && value > 0 } return if stats.empty? counters.transform_keys! { |key| key.to_s.delete_prefix(prefix) } key_pad = counters.keys.map(&:size).max value_pad = counters.values.map { |value| number_with_delimiter(value).size }.max total = counters.values.sum counters = counters.to_a counters.sort_by! { |_, value| -value } counters = counters.first(limit) if limit 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 283
def print_stats $stderr.write stats_string end
.reset_stats!
Discard statistics collected for –zjit-stats.
# File 'zjit.rb', line 145
def reset_stats! Primitive.rb_zjit_reset_stats_bang end
.stats(target_key = nil)
Return ZJIT statistics as a ::Hash
# File 'zjit.rb', line 140
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 150
def stats_string buf = +"***ZJIT: Printing ZJIT statistics on exit***\n" stats = self.stats stats[:guard_type_exit_ratio] = stats[:exit_guard_type_failure].to_f / stats[:guard_type_count] * 100 # 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) # 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_send_without_block_def_type_', prompt: 'not optimized method types for send_without_block', buf:, stats:, limit: 20) print_counters_with_prefix(prefix: 'not_optimized_yarv_insn_', prompt: 'not optimized instructions', buf:, stats:, limit: 20) print_counters_with_prefix(prefix: 'send_fallback_', prompt: 'send fallback reasons', buf:, stats:, limit: 20) # Show exit counters, ordered by the typical amount of exits for the prefix at the time print_counters_with_prefix(prefix: 'unhandled_yarv_insn_', prompt: 'unhandled YARV insns', buf:, stats:, limit: 20) print_counters_with_prefix(prefix: 'compile_error_', prompt: 'compile error reasons', 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, :non_variadic_cfunc_optimized_send_count, :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, :compile_time_ns, :profile_time_ns, :gc_time_ns, :invalidation_time_ns, :vm_write_pc_count, :vm_write_sp_count, :vm_write_locals_count, :vm_write_stack_count, :vm_write_to_parent_iseq_local_count, :vm_read_from_parent_iseq_local_count, :guard_type_count, :guard_type_exit_ratio, :code_region_bytes, :side_exit_count, :total_insn_count, :vm_insn_count, :zjit_insn_count, :ratio_in_zjit, ], buf:, stats:) buf end