Overview
YJIT (Yet Another Ruby Just-In-Time compiler) compiles frequently executed Ruby code to native machine code during runtime. Ruby enables YJIT through command-line options, environment variables, and runtime configuration methods. The compiler monitors method execution patterns and selectively compiles hot code paths to improve performance.
Ruby activates YJIT with the --yjit
command-line flag or by setting the RUBY_YJIT_ENABLE
environment variable. Once enabled, YJIT tracks method calls and basic block execution frequency. When execution counts exceed configured thresholds, YJIT compiles the code to optimized machine instructions.
The configuration system controls compilation behavior, memory usage limits, and debugging output. YJIT maintains separate configuration for development and production environments through environment variables and runtime options.
# Enable YJIT at startup
# ruby --yjit script.rb
# Check YJIT status
puts RubyVM::YJIT.enabled?
# => true or false
# Runtime statistics
stats = RubyVM::YJIT.runtime_stats
puts stats[:compiled_iseq_count]
# => number of compiled instruction sequences
YJIT configuration affects three primary areas: compilation thresholds that determine when code gets compiled, memory limits that constrain JIT compilation overhead, and instrumentation settings that control performance monitoring and debugging output.
Basic Usage
Enabling YJIT
Ruby provides multiple methods to activate YJIT compilation. The most common approach uses the command-line flag when starting Ruby applications:
# Command line activation
# ruby --yjit application.rb
# Environment variable activation
# RUBY_YJIT_ENABLE=1 ruby application.rb
# Runtime detection
if RubyVM::YJIT.enabled?
puts "YJIT compilation active"
puts "Compiled methods: #{RubyVM::YJIT.runtime_stats[:compiled_iseq_count]}"
end
Runtime Configuration
YJIT accepts configuration through environment variables that modify compilation behavior. These variables must be set before Ruby process startup:
# Set compilation threshold (default: 30)
# RUBY_YJIT_CALL_THRESHOLD=50 ruby --yjit app.rb
# Limit compiled code size (default: 128MB)
# RUBY_YJIT_CODE_GC_THRESHOLD=64 ruby --yjit app.rb
# Enable execution tracing
# RUBY_YJIT_TRACE_EXITS=1 ruby --yjit app.rb
# Runtime configuration check
def yjit_config_info
return "YJIT disabled" unless RubyVM::YJIT.enabled?
stats = RubyVM::YJIT.runtime_stats
{
compiled_methods: stats[:compiled_iseq_count],
code_size: stats[:code_region_size],
invalidations: stats[:invalidation_count]
}
end
puts yjit_config_info
Compilation Thresholds
YJIT tracks method execution frequency and compiles methods that exceed the call threshold. The default threshold balances compilation overhead against performance benefits:
class BenchmarkTarget
def hot_method(data)
data.map(&:upcase).select { |s| s.length > 5 }
end
def cold_method
"This method runs infrequently"
end
end
target = BenchmarkTarget.new
test_data = ["hello", "world", "ruby", "programming"]
# Trigger compilation through repeated calls
1000.times { target.hot_method(test_data) }
# Check compilation status
stats = RubyVM::YJIT.runtime_stats
puts "Compiled instruction sequences: #{stats[:compiled_iseq_count]}"
puts "Compiled method bodies: #{stats[:compiled_method_count]}"
Memory Management
YJIT allocates memory regions for compiled code and maintains metadata for optimization decisions. The code garbage collector reclaims memory when limits are exceeded:
# Monitor YJIT memory usage
def yjit_memory_stats
return {} unless RubyVM::YJIT.enabled?
stats = RubyVM::YJIT.runtime_stats
{
code_region_size: stats[:code_region_size],
outlined_code_size: stats[:outlined_code_size],
code_gc_count: stats[:code_gc_count],
inline_code_size: stats[:inline_code_size]
}
end
# Before heavy computation
initial_memory = yjit_memory_stats
puts "Initial code size: #{initial_memory[:code_region_size]} bytes"
# Execute code that triggers compilation
10_000.times do |i|
Math.sqrt(i) + Math.log(i + 1)
end
# After compilation
final_memory = yjit_memory_stats
puts "Final code size: #{final_memory[:code_region_size]} bytes"
puts "Memory growth: #{final_memory[:code_region_size] - initial_memory[:code_region_size]} bytes"
Performance & Memory
Compilation Impact Measurement
YJIT performance benefits vary significantly based on code patterns and execution characteristics. Methods with tight loops, mathematical computations, and repeated object allocations typically see the largest improvements:
require 'benchmark'
class PerformanceTest
def array_processing(size)
(1..size).map { |n| n * n + Math.sqrt(n) }.select(&:even?)
end
def string_manipulation(text, iterations)
result = text
iterations.times do
result = result.upcase.gsub(/[aeiou]/, '*').reverse
end
result
end
def numeric_computation(limit)
sum = 0
(1..limit).each do |i|
sum += Math.sin(i) * Math.cos(i) + i**0.5
end
sum
end
end
test = PerformanceTest.new
# Warm up YJIT compilation
100.times { test.array_processing(1000) }
100.times { test.string_manipulation("test string", 100) }
100.times { test.numeric_computation(1000) }
# Performance comparison
Benchmark.bm(20) do |x|
x.report("Array processing:") do
1000.times { test.array_processing(1000) }
end
x.report("String manipulation:") do
1000.times { test.string_manipulation("ruby programming", 50) }
end
x.report("Numeric computation:") do
1000.times { test.numeric_computation(500) }
end
end
# Display compilation statistics
stats = RubyVM::YJIT.runtime_stats
puts "\nCompilation Statistics:"
puts "Compiled methods: #{stats[:compiled_method_count]}"
puts "Code region size: #{stats[:code_region_size]} bytes"
puts "Exit locations: #{stats[:traced_exits_count]}"
Memory Usage Optimization
YJIT maintains several memory regions for different types of compiled code. Understanding these regions helps optimize memory usage in memory-constrained environments:
def analyze_yjit_memory_usage
return "YJIT not enabled" unless RubyVM::YJIT.enabled?
stats = RubyVM::YJIT.runtime_stats
total_memory = stats[:code_region_size] + stats[:outlined_code_size]
analysis = {
inline_code: {
size: stats[:inline_code_size],
percentage: (stats[:inline_code_size].to_f / total_memory * 100).round(2)
},
outlined_code: {
size: stats[:outlined_code_size],
percentage: (stats[:outlined_code_size].to_f / total_memory * 100).round(2)
},
total_compiled: total_memory,
gc_collections: stats[:code_gc_count],
freed_pages: stats[:freed_code_pages]
}
analysis
end
# Before and after memory analysis
initial_analysis = analyze_yjit_memory_usage
# Execute memory-intensive compilation workload
class MemoryIntensiveClass
1000.times do |i|
define_method("method_#{i}") do |param|
param.to_s.length * i + Math.sqrt(param)
end
end
end
obj = MemoryIntensiveClass.new
1000.times do |i|
obj.send("method_#{i % 1000}", rand(100))
end
final_analysis = analyze_yjit_memory_usage
puts "Memory usage analysis:"
puts "Total memory: #{final_analysis[:total_compiled]} bytes"
puts "Inline code: #{final_analysis[:inline_code][:size]} bytes (#{final_analysis[:inline_code][:percentage]}%)"
puts "Outlined code: #{final_analysis[:outlined_code][:size]} bytes (#{final_analysis[:outlined_code][:percentage]}%)"
puts "GC collections: #{final_analysis[:gc_collections]}"
Compilation Overhead Analysis
YJIT compilation introduces overhead during the initial execution phase while methods are being compiled. This overhead typically pays off through improved execution speed:
class CompilationOverheadTest
def measure_compilation_phases(method_name, iterations)
times = []
# Cold execution (compilation phase)
cold_start = Time.now
30.times { send(method_name) }
cold_time = Time.now - cold_start
# Warm execution (compiled code)
warm_start = Time.now
iterations.times { send(method_name) }
warm_time = Time.now - warm_start
{
cold_phase: cold_time,
warm_phase: warm_time,
per_call_cold: cold_time / 30,
per_call_warm: warm_time / iterations,
speedup: (cold_time / 30) / (warm_time / iterations)
}
end
def cpu_intensive_method
result = 0
1000.times do |i|
result += Math.sin(i) * Math.cos(i) + i**2
end
result
end
def memory_allocation_method
arrays = []
100.times do |i|
arrays << (1..i).to_a.map(&:to_s)
end
arrays.flatten.size
end
end
test = CompilationOverheadTest.new
cpu_results = test.measure_compilation_phases(:cpu_intensive_method, 1000)
memory_results = test.measure_compilation_phases(:memory_allocation_method, 1000)
puts "CPU-intensive method results:"
puts "Cold phase: #{(cpu_results[:per_call_cold] * 1000).round(3)}ms per call"
puts "Warm phase: #{(cpu_results[:per_call_warm] * 1000).round(3)}ms per call"
puts "Speedup: #{cpu_results[:speedup].round(2)}x"
puts "\nMemory allocation method results:"
puts "Cold phase: #{(memory_results[:per_call_cold] * 1000).round(3)}ms per call"
puts "Warm phase: #{(memory_results[:per_call_warm] * 1000).round(3)}ms per call"
puts "Speedup: #{memory_results[:speedup].round(2)}x"
Production Patterns
Application Server Integration
YJIT configuration in production web applications requires coordination with application servers and deployment processes. Most application servers support YJIT through environment variable configuration:
# config/application.rb or deployment script
class ProductionYJITConfig
def self.configure_for_environment
return unless production_environment?
ENV['RUBY_YJIT_ENABLE'] = '1'
ENV['RUBY_YJIT_CALL_THRESHOLD'] = '50' # Higher threshold for stability
ENV['RUBY_YJIT_CODE_GC_THRESHOLD'] = '256' # More memory for large apps
# Disable tracing in production for performance
ENV.delete('RUBY_YJIT_TRACE_EXITS')
end
def self.production_environment?
ENV['RAILS_ENV'] == 'production' || ENV['RACK_ENV'] == 'production'
end
def self.yjit_health_check
return { status: 'disabled', reason: 'YJIT not enabled' } unless RubyVM::YJIT.enabled?
stats = RubyVM::YJIT.runtime_stats
memory_mb = stats[:code_region_size] / (1024 * 1024)
health_status = {
status: 'healthy',
compiled_methods: stats[:compiled_method_count],
memory_usage_mb: memory_mb,
invalidations: stats[:invalidation_count],
code_gc_count: stats[:code_gc_count]
}
# Alert conditions
if memory_mb > 200
health_status[:warnings] = ['High memory usage']
end
if stats[:invalidation_count] > stats[:compiled_method_count] * 0.1
health_status[:warnings] ||= []
health_status[:warnings] << 'High invalidation rate'
end
health_status
end
end
# Puma server configuration example
# config/puma.rb
on_worker_boot do
ProductionYJITConfig.configure_for_environment
end
# Health check endpoint
get '/health/yjit' do
content_type :json
ProductionYJITConfig.yjit_health_check.to_json
end
Monitoring and Alerting
Production YJIT deployments require monitoring of compilation rates, memory usage, and performance regression detection:
class YJITMonitoring
def initialize(statsd_client = nil)
@statsd = statsd_client
@baseline_stats = initial_stats_snapshot
end
def collect_metrics
return unless RubyVM::YJIT.enabled?
current_stats = RubyVM::YJIT.runtime_stats
metrics = calculate_metrics(current_stats)
report_to_monitoring(metrics) if @statsd
metrics
end
private
def initial_stats_snapshot
return {} unless RubyVM::YJIT.enabled?
RubyVM::YJIT.runtime_stats.dup
end
def calculate_metrics(current_stats)
{
compilation_rate: compilation_rate(current_stats),
memory_efficiency: memory_efficiency(current_stats),
invalidation_rate: invalidation_rate(current_stats),
code_gc_pressure: code_gc_pressure(current_stats)
}
end
def compilation_rate(stats)
total_executed = stats[:exec_instruction] || 1
compiled_ratio = stats[:compiled_iseq_count].to_f / total_executed
(compiled_ratio * 100).round(2)
end
def memory_efficiency(stats)
total_memory = stats[:code_region_size] + stats[:outlined_code_size]
return 0 if total_memory == 0
methods_per_mb = stats[:compiled_method_count].to_f / (total_memory / (1024 * 1024))
methods_per_mb.round(2)
end
def invalidation_rate(stats)
return 0 if stats[:compiled_method_count] == 0
invalidation_ratio = stats[:invalidation_count].to_f / stats[:compiled_method_count]
(invalidation_ratio * 100).round(2)
end
def code_gc_pressure(stats)
baseline_gcs = @baseline_stats[:code_gc_count] || 0
current_gcs = stats[:code_gc_count] || 0
current_gcs - baseline_gcs
end
def report_to_monitoring(metrics)
@statsd.gauge('yjit.compilation_rate', metrics[:compilation_rate])
@statsd.gauge('yjit.memory_efficiency', metrics[:memory_efficiency])
@statsd.gauge('yjit.invalidation_rate', metrics[:invalidation_rate])
@statsd.gauge('yjit.code_gc_pressure', metrics[:code_gc_pressure])
end
end
# Background monitoring job
class YJITMetricsJob
def self.perform
monitor = YJITMonitoring.new($statsd_client)
metrics = monitor.collect_metrics
# Log significant changes
if metrics[:invalidation_rate] > 15
Rails.logger.warn("High YJIT invalidation rate: #{metrics[:invalidation_rate]}%")
end
if metrics[:code_gc_pressure] > 10
Rails.logger.warn("Frequent YJIT code GC: #{metrics[:code_gc_pressure]} collections")
end
end
end
Load Testing and Performance Validation
Production YJIT deployments require comprehensive performance validation to ensure compilation improves application throughput:
class YJITLoadTest
def initialize(endpoints)
@endpoints = endpoints
@request_count = 0
@response_times = []
end
def run_load_test(duration_seconds, concurrent_requests)
test_start = Time.now
threads = []
concurrent_requests.times do
threads << Thread.new do
while Time.now - test_start < duration_seconds
endpoint = @endpoints.sample
response_time = measure_request(endpoint)
Thread.current[:responses] ||= []
Thread.current[:responses] << response_time
end
end
end
threads.each(&:join)
# Collect results from all threads
all_responses = threads.flat_map { |t| t[:responses] || [] }
generate_performance_report(all_responses, duration_seconds)
end
private
def measure_request(endpoint)
start_time = Time.now
# Simulate application request
case endpoint[:type]
when :computation
perform_computation_work
when :database
simulate_database_work
when :api
simulate_api_processing
end
Time.now - start_time
end
def perform_computation_work
result = 0
1000.times do |i|
result += Math.sqrt(i) * Math.sin(i) + i**2
end
result
end
def simulate_database_work
# Simulate ORM-style object processing
data = Array.new(100) { |i| { id: i, value: "item_#{i}", score: rand(100) } }
data.select { |item| item[:score] > 50 }
.sort_by { |item| item[:score] }
.map { |item| item[:value].upcase }
end
def simulate_api_processing
# Simulate JSON processing and validation
json_data = { users: Array.new(50) { |i| { name: "User #{i}", email: "user#{i}@example.com" } } }
json_data[:users].map { |user| user[:email].include?('@') }.all?
end
def generate_performance_report(response_times, duration)
sorted_times = response_times.sort
{
duration_seconds: duration,
total_requests: response_times.length,
requests_per_second: response_times.length.to_f / duration,
average_response_time: (response_times.sum / response_times.length * 1000).round(2),
median_response_time: (sorted_times[sorted_times.length / 2] * 1000).round(2),
p95_response_time: (sorted_times[(sorted_times.length * 0.95).to_i] * 1000).round(2),
p99_response_time: (sorted_times[(sorted_times.length * 0.99).to_i] * 1000).round(2),
yjit_stats: RubyVM::YJIT.enabled? ? RubyVM::YJIT.runtime_stats : 'disabled'
}
end
end
# Performance comparison test
endpoints = [
{ type: :computation, weight: 0.4 },
{ type: :database, weight: 0.4 },
{ type: :api, weight: 0.2 }
]
load_test = YJITLoadTest.new(endpoints)
results = load_test.run_load_test(60, 10) # 60 seconds, 10 concurrent requests
puts "Load Test Results:"
puts "Requests per second: #{results[:requests_per_second].round(2)}"
puts "Average response time: #{results[:average_response_time]}ms"
puts "95th percentile: #{results[:p95_response_time]}ms"
puts "99th percentile: #{results[:p99_response_time]}ms"
if results[:yjit_stats] != 'disabled'
puts "YJIT compiled methods: #{results[:yjit_stats][:compiled_method_count]}"
puts "YJIT memory usage: #{(results[:yjit_stats][:code_region_size] / 1024.0 / 1024.0).round(2)}MB"
end
Error Handling & Debugging
Compilation Failure Diagnosis
YJIT may fail to compile certain code patterns or encounter internal errors during optimization. Understanding these failures helps identify problematic code sections:
class YJITDebugger
def self.analyze_compilation_issues
return "YJIT not enabled" unless RubyVM::YJIT.enabled?
stats = RubyVM::YJIT.runtime_stats
analysis = {
total_methods_seen: stats[:binding_allocations] || 0,
successfully_compiled: stats[:compiled_method_count],
compilation_failures: detect_compilation_failures(stats),
exit_reasons: analyze_exit_patterns(stats),
invalidation_causes: analyze_invalidations(stats)
}
generate_debug_recommendations(analysis)
end
def self.detect_compilation_failures(stats)
# Estimate failures from difference in seen vs compiled methods
seen_methods = stats[:binding_allocations] || 0
compiled_methods = stats[:compiled_method_count] || 0
failure_estimate = [seen_methods - compiled_methods, 0].max
{
estimated_failures: failure_estimate,
success_rate: seen_methods > 0 ? (compiled_methods.to_f / seen_methods * 100).round(2) : 0
}
end
def self.analyze_exit_patterns(stats)
# Common exit reasons indicate why YJIT falls back to interpreter
{
traced_exits: stats[:traced_exits_count] || 0,
exit_locations: stats[:exit_locations] || 0,
side_exits: stats[:side_exit_count] || 0
}
end
def self.analyze_invalidations(stats)
invalidation_count = stats[:invalidation_count] || 0
compiled_count = stats[:compiled_method_count] || 1
{
total_invalidations: invalidation_count,
invalidation_rate: (invalidation_count.to_f / compiled_count * 100).round(2),
constant_invalidations: stats[:constant_invalidations] || 0
}
end
def self.generate_debug_recommendations(analysis)
recommendations = []
if analysis[:compilation_failures][:success_rate] < 50
recommendations << "Low compilation success rate suggests complex metaprogramming or unsupported patterns"
end
if analysis[:exit_reasons][:traced_exits] > analysis[:successfully_compiled] * 10
recommendations << "High exit rate indicates type instability or polymorphic call sites"
end
if analysis[:invalidation_causes][:invalidation_rate] > 10
recommendations << "Frequent invalidations suggest dynamic constant modification or monkey patching"
end
analysis.merge(recommendations: recommendations)
end
end
# Method-specific compilation tracking
class CompilationTracker
def self.track_method_compilation(klass, method_name)
original_method = klass.instance_method(method_name)
call_count = 0
klass.define_method(method_name) do |*args, **kwargs|
call_count += 1
if call_count == 30 # Default compilation threshold
puts "Method #{klass}##{method_name} should compile after this call"
before_stats = RubyVM::YJIT.runtime_stats
result = original_method.bind(self).call(*args, **kwargs)
after_stats = RubyVM::YJIT.runtime_stats
if after_stats[:compiled_method_count] > before_stats[:compiled_method_count]
puts "✓ Method compiled successfully"
else
puts "✗ Method compilation may have failed"
end
result
else
original_method.bind(self).call(*args, **kwargs)
end
end
end
end
# Usage example
class TestClass
def problematic_method(param)
# Code patterns that might prevent compilation
if param.respond_to?(:each)
param.each_with_object({}) do |item, hash|
# Dynamic method calls can prevent optimization
hash[item.send(:to_s)] = item.send([:length, :size].sample)
end
else
param.to_s.length
end
end
end
CompilationTracker.track_method_compilation(TestClass, :problematic_method)
obj = TestClass.new
35.times { |i| obj.problematic_method(["test", "array"]) } # Trigger compilation tracking
Performance Regression Detection
YJIT can occasionally introduce performance regressions due to compilation overhead or suboptimal code generation. Systematic performance monitoring detects these issues:
class PerformanceRegressionDetector
def initialize
@baseline_measurements = {}
@regression_threshold = 0.15 # 15% performance drop threshold
end
def establish_baseline(test_name, test_proc, iterations = 1000)
# Disable YJIT for baseline measurement
if RubyVM::YJIT.enabled?
puts "Warning: YJIT enabled during baseline measurement"
end
times = []
iterations.times do
start_time = Time.now
test_proc.call
times << Time.now - start_time
end
@baseline_measurements[test_name] = {
mean: times.sum / times.length,
median: times.sort[times.length / 2],
p95: times.sort[(times.length * 0.95).to_i],
iterations: iterations
}
end
def measure_with_yjit(test_name, test_proc, warmup_iterations = 100, measurement_iterations = 1000)
return "YJIT not enabled" unless RubyVM::YJIT.enabled?
return "No baseline for #{test_name}" unless @baseline_measurements[test_name]
# Warm up compilation
warmup_iterations.times { test_proc.call }
# Measure performance
times = []
measurement_iterations.times do
start_time = Time.now
test_proc.call
times << Time.now - start_time
end
current_measurement = {
mean: times.sum / times.length,
median: times.sort[times.length / 2],
p95: times.sort[(times.length * 0.95).to_i],
iterations: measurement_iterations
}
analyze_regression(test_name, current_measurement)
end
private
def analyze_regression(test_name, current_measurement)
baseline = @baseline_measurements[test_name]
mean_change = (current_measurement[:mean] - baseline[:mean]) / baseline[:mean]
median_change = (current_measurement[:median] - baseline[:median]) / baseline[:median]
p95_change = (current_measurement[:p95] - baseline[:p95]) / baseline[:p95]
regression_detected = [mean_change, median_change, p95_change].any? { |change| change > @regression_threshold }
{
test_name: test_name,
regression_detected: regression_detected,
performance_changes: {
mean: (mean_change * 100).round(2),
median: (median_change * 100).round(2),
p95: (p95_change * 100).round(2)
},
baseline: baseline,
current: current_measurement,
yjit_stats: RubyVM::YJIT.runtime_stats
}
end
end
# Regression testing example
detector = PerformanceRegressionDetector.new
# Define test cases
array_processing_test = proc do
data = (1..1000).to_a
data.map { |n| n * 2 + 1 }.select(&:even?).sum
end
string_processing_test = proc do
text = "Ruby YJIT performance testing"
100.times do
text = text.reverse.upcase.gsub(/[AEIOU]/, '*').downcase
end
text
end
# Establish baselines (run with YJIT disabled)
unless RubyVM::YJIT.enabled?
detector.establish_baseline("array_processing", array_processing_test)
detector.establish_baseline("string_processing", string_processing_test)
puts "Baselines established. Restart with YJIT enabled for regression testing."
else
# Run regression tests (with YJIT enabled)
array_results = detector.measure_with_yjit("array_processing", array_processing_test)
string_results = detector.measure_with_yjit("string_processing", string_processing_test)
[array_results, string_results].each do |result|
next unless result.is_a?(Hash)
puts "\nTest: #{result[:test_name]}"
puts "Regression detected: #{result[:regression_detected]}"
puts "Performance changes:"
puts " Mean: #{result[:performance_changes][:mean]}%"
puts " Median: #{result[:performance_changes][:median]}%"
puts " 95th percentile: #{result[:performance_changes][:p95]}%"
if result[:regression_detected]
puts "⚠️ Performance regression detected!"
puts "YJIT compiled methods: #{result[:yjit_stats][:compiled_method_count]}"
puts "YJIT invalidations: #{result[:yjit_stats][:invalidation_count]}"
end
end
end
Reference
Environment Variables
Variable | Default | Description |
---|---|---|
RUBY_YJIT_ENABLE |
0 |
Enable YJIT compilation (1 to enable) |
RUBY_YJIT_CALL_THRESHOLD |
30 |
Method call count before compilation |
RUBY_YJIT_CODE_GC_THRESHOLD |
128 |
Code region size limit in MB before GC |
RUBY_YJIT_TRACE_EXITS |
0 |
Enable exit tracing (1 to enable) |
RUBY_YJIT_GREEDY_VERSIONING |
0 |
Compile more aggressively (1 to enable) |
RUBY_YJIT_STATS |
0 |
Collect detailed statistics (1 to enable) |
Runtime Statistics Methods
Method | Returns | Description |
---|---|---|
RubyVM::YJIT.enabled? |
Boolean |
Check if YJIT is active |
RubyVM::YJIT.runtime_stats |
Hash |
Complete statistics hash |
RubyVM::YJIT.reset_stats! |
nil |
Reset all statistics counters |
RubyVM::YJIT.enable |
Boolean |
Enable YJIT at runtime (limited) |
RubyVM::YJIT.code_gc |
Integer |
Force code garbage collection |
Statistics Hash Keys
Key | Type | Description |
---|---|---|
:compiled_iseq_count |
Integer |
Number of compiled instruction sequences |
:compiled_method_count |
Integer |
Number of compiled method bodies |
:code_region_size |
Integer |
Total compiled code size in bytes |
:inline_code_size |
Integer |
Inline code region size in bytes |
:outlined_code_size |
Integer |
Outlined code region size in bytes |
:invalidation_count |
Integer |
Number of compilation invalidations |
:constant_invalidations |
Integer |
Invalidations due to constant changes |
:code_gc_count |
Integer |
Number of code garbage collections |
:freed_code_pages |
Integer |
Pages freed during code GC |
:exit_locations |
Integer |
Number of unique exit locations |
:traced_exits_count |
Integer |
Total traced exits from compiled code |
:side_exit_count |
Integer |
Side exits to interpreter |
:exec_instruction |
Integer |
Total executed instructions |
:binding_allocations |
Integer |
Method binding allocations tracked |
Command Line Options
Option | Description |
---|---|
--yjit |
Enable YJIT compilation |
--yjit-stats |
Enable YJIT statistics collection |
--yjit-trace-exits |
Enable exit location tracing |
--yjit-call-threshold=N |
Set compilation threshold to N calls |
--yjit-code-gc |
Enable code garbage collection |
Performance Characteristics
Code Pattern | YJIT Impact | Compilation Likelihood |
---|---|---|
Tight loops with arithmetic | High positive | Very likely |
Array/Hash processing | Moderate positive | Likely |
String manipulation | Moderate positive | Likely |
Method calls with stable types | Moderate positive | Likely |
Dynamic method dispatch | Low/negative | Unlikely |
Heavy metaprogramming | Negative | Very unlikely |
Constant modification | Negative | Causes invalidation |
Polymorphic call sites | Low positive | May cause exits |
Memory Usage Guidelines
Application Size | Recommended Code GC Threshold | Expected Memory Usage |
---|---|---|
Small applications | 64MB | 10-50MB |
Medium web applications | 128MB (default) | 50-150MB |
Large web applications | 256MB | 100-300MB |
Memory-constrained environments | 32MB | 5-30MB |
Troubleshooting Checklist
Issue | Diagnostic Steps | Common Solutions |
---|---|---|
YJIT not improving performance | Check compilation rates, measure warmup time | Increase call threshold, verify hot paths |
High memory usage | Monitor code region size, GC frequency | Lower code GC threshold, check for leaks |
Frequent invalidations | Track constant modifications, method redefinitions | Avoid runtime constant changes |
Compilation failures | Enable exit tracing, check for unsupported patterns | Refactor dynamic code, reduce metaprogramming |
Performance regressions | Compare with/without YJIT, profile exit patterns | Adjust thresholds, identify problematic methods |