Ruby Logo

MRI (CRuby) - Standard Ruby Implementation

Master the standard Ruby implementation, understand the Global Interpreter Lock (GIL), and explore Ruby's internal architecture.

Home Ruby MRI (CRuby) - Standard Ruby Implementation

MRI (CRuby) - Standard Ruby Implementation

Master the standard Ruby implementation, understand the Global Interpreter Lock (GIL), and explore Ruby's internal architecture.

What is MRI (CRuby)?

MRI (Matz's Ruby Implementation) is the reference implementation of Ruby, written in C by Ruby's creator Yukihiro "Matz" Matsumoto.

Key Characteristics

  • Reference Implementation: The "official" Ruby implementation that defines Ruby's behavior
  • Written in C: Core interpreter written in C for performance and portability
  • YARV VM: Uses Yet Another Ruby Virtual Machine for bytecode execution
  • Global Interpreter Lock: Single-threaded execution model with GIL
  • C Extension Support: Native support for C extensions
  • Cross-Platform: Runs on Unix, Linux, macOS, Windows

Ruby Version Evolution

Ruby 3.x (Current)

  • Ruby 3.3: Latest stable (YJIT improvements, better performance)
  • Ruby 3.2: WASI support, Data class, improved YJIT
  • Ruby 3.1: YJIT JIT compiler, debug gem, error highlighting
  • Ruby 3.0: Pattern matching, Ractor, static analysis, 3x performance goal

Ruby 2.x (Legacy)

  • Ruby 2.7: Pattern matching (experimental), numbered parameters
  • Ruby 2.6: JIT compiler, endless ranges
  • Ruby 2.5: rescue/else/ensure in blocks
  • Ruby 2.0: Keyword arguments, refinements

Global Interpreter Lock (GIL)

Important: The GIL is one of the most important concepts to understand in MRI Ruby threading.

What is the GIL?

The Global Interpreter Lock is a mutex that prevents multiple threads from executing Ruby code simultaneously. Only one thread can execute Ruby bytecode at a time.

Why does the GIL exist?
  • Memory Safety: Protects Ruby's memory management from race conditions
  • C Extension Safety: Many C extensions are not thread-safe
  • Simplicity: Makes Ruby's object model and garbage collector simpler
  • Compatibility: Ensures existing code continues to work
GIL Implications
  • CPU-bound tasks: No true parallelism for Ruby code
  • I/O operations: GIL is released during I/O, allowing concurrency
  • C extensions: Can release GIL for true parallel execution
  • Multiple processes: Use processes instead of threads for CPU-intensive work

YARV Virtual Machine

YARV (Yet Another Ruby Virtual Machine) is the bytecode interpreter that executes Ruby code in MRI.

Execution Pipeline

  1. Parsing: Ruby source code → Abstract Syntax Tree (AST)
  2. Compilation: AST → YARV bytecode instructions
  3. Execution: YARV VM executes bytecode
  4. Optimization: YJIT (Ruby 3.1+) compiles hot code to machine code

Inspecting Bytecode

# View compiled bytecode
puts RubyVM::InstructionSequence.compile("puts 'Hello, World!'").disasm

# Compile and save bytecode
iseq = RubyVM::InstructionSequence.compile_file("script.rb")
File.write("script.yarv", iseq.to_binary)

Memory Management & Garbage Collection

Object Allocation

  • Heap Management: Objects allocated in memory heaps
  • Object Slots: Fixed-size slots for different object types
  • Immediate Values: Small integers, symbols stored directly
  • Copy-on-Write: Memory optimization for forked processes

Garbage Collection

  • Mark & Sweep: Tri-color marking algorithm
  • Generational GC: Young and old object generations
  • Incremental GC: Reduces pause times
  • Compaction: Memory defragmentation (Ruby 2.7+)

GC Tuning Example

# Environment variables for GC tuning
ENV['RUBY_GC_HEAP_INIT_SLOTS'] = '10000'
ENV['RUBY_GC_HEAP_FREE_SLOTS'] = '2000'
ENV['RUBY_GC_HEAP_GROWTH_FACTOR'] = '1.2'
ENV['RUBY_GC_HEAP_GROWTH_MAX_SLOTS'] = '100000'

# Monitor GC statistics
GC.stat.each { |k, v| puts "#{k}: #{v}" }

# Force garbage collection
GC.start

# Disable/enable GC temporarily
GC.disable
# ... memory intensive operations
GC.enable

Performance Characteristics

Strengths

  • I/O Concurrency: Excellent for I/O-bound applications
  • Memory Efficiency: Generally lower memory usage
  • Compatibility: Broadest gem ecosystem support
  • Debugging: Excellent debugging tools and profilers
  • YJIT: JIT compilation for performance gains (Ruby 3.1+)

Limitations

  • CPU-bound Performance: GIL limits parallel CPU processing
  • Thread Overhead: Thread creation and context switching costs
  • Memory Growth: Can exhibit memory growth in long-running processes
  • JIT Warmup: YJIT requires warmup time for optimization

YJIT Just-In-Time Compiler

YJIT is Ruby's built-in JIT compiler that compiles frequently executed Ruby code to optimized machine code.

Enabling YJIT

# Enable YJIT at runtime
ruby --yjit script.rb

# Enable with environment variable
RUBYOPT="--yjit" ruby script.rb

# Check if YJIT is enabled
puts RubyVM::YJIT.enabled?

# Get YJIT statistics
puts RubyVM::YJIT.runtime_stats

YJIT Performance Tips

  • Warmup Period: YJIT needs time to identify hot code paths
  • Long-running Processes: Best for applications that run for extended periods
  • CPU-intensive Code: Most beneficial for computation-heavy Ruby code
  • Method Calls: Optimizes frequent method calls and loops

Development & Debugging Tools

Built-in Debugging

# Ruby 3.1+ debug gem
require 'debug'
binding.break  # Set breakpoint

# Traditional debugging
require 'pry'
binding.pry

# Inspect object allocation
require 'objspace'
ObjectSpace.trace_object_allocations_start
# ... your code
ObjectSpace.dump_all(output: File.open('heap.json', 'w'))

Profiling Tools

# Stack profiling
require 'stackprof'
StackProf.run(mode: :cpu, out: 'stackprof.dump') do
  # Your code here
end

# Memory profiling
require 'memory_profiler'
report = MemoryProfiler.report do
  # Your code here
end
report.pretty_print

MRI Best Practices

Performance Optimization

  • Use Processes: For CPU-intensive parallel work
  • Async I/O: Leverage non-blocking I/O for concurrency
  • Memory Management: Profile and tune garbage collection
  • Enable YJIT: For long-running applications

Threading Guidelines

  • I/O Operations: Threads work well for I/O-bound tasks
  • Thread Pools: Reuse threads to avoid creation overhead
  • Shared State: Minimize shared mutable state
  • Synchronization: Use Mutex for thread safety when needed

Production Considerations

  • Memory Monitoring: Watch for memory leaks and growth
  • GC Tuning: Adjust GC settings for your workload
  • Process Management: Use process managers like Puma, Unicorn
  • Resource Limits: Set appropriate ulimits and memory limits

When to Choose MRI

Ideal Use Cases

  • Web Applications: Rails, Sinatra, and other web frameworks
  • API Services: REST and GraphQL APIs
  • I/O-Heavy Applications: File processing, network operations
  • Scripting: Automation scripts and DevOps tools
  • Prototyping: Rapid application development

Consider Alternatives For

  • CPU-Intensive Parallel Processing: Consider JRuby or TruffleRuby
  • Memory-Constrained Environments: May need careful tuning
  • Java Integration: JRuby provides better Java interop
  • Maximum Performance: TruffleRuby may offer better peak performance

Quick Navigation

Related Topics

Video Tutorial

Watch and learn mri (cruby) - standard ruby implementation

Pro Tip: After reading through the content above, watch this video to reinforce your understanding and see the concepts in action!