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
Source
# File zjit.rb, line 124 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
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.
Source
# File zjit.rb, line 21 def enabled? Primitive.cexpr! 'RBOOL(rb_zjit_enabled_p)' end
Check if ZJIT is enabled
Source
# 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].dup line_samples = results[:lines].dup frames = results[:frames].dup samples_count = 0 # Loop through the instructions and set the frame hash with the data. # We use nonexistent.def for the file name, otherwise insns.def will be displayed # and that information isn't useful in this context. RubyVM::INSTRUCTION_NAMES.each_with_index do |name, frame_id| frame_hash = { samples: 0, total_samples: 0, edges: {}, name: name, file: "nonexistent.def", line: nil, lines: {} } results[:frames][frame_id] = frame_hash frames[frame_id] = frame_hash 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 # Set missed_samples and gc_samples to 0 as their values # don't matter to us in this context. results[:missed_samples] = 0 results[:gc_samples] = 0 results end
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.
Source
# File zjit.rb, line 146 def reset_stats! Primitive.rb_zjit_reset_stats_bang end
Discard statistics collected for ‘–zjit-stats`.
Source
# File zjit.rb, line 141 def stats(target_key = nil) Primitive.rb_zjit_stats(target_key) end
Return ZJIT statistics as a Hash
Source
# File zjit.rb, line 136 def stats_enabled? Primitive.rb_zjit_stats_enabled_p end
Check if ‘–zjit-stats` is used
Source
# File zjit.rb, line 151 def stats_string buf = +"***ZJIT: Printing ZJIT statistics on exit***\n" stats = self.stats # 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) # Show fallback counters, ordered by the typical amount of fallbacks for the prefix at the time print_counters_with_prefix(prefix: 'unspecialized_def_type_', prompt: 'not optimized method types', 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, :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, :code_region_bytes, :side_exit_count, :total_insn_count, :vm_insn_count, :zjit_insn_count, :ratio_in_zjit, ], buf:, stats:) buf end
Get the summary of ZJIT statistics as a String
Source
# File zjit.rb, line 26 def trace_exit_locations_enabled? Primitive.rb_zjit_trace_exit_locations_enabled_p end
Check if ‘–zjit-trace-exits` is used