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
- Parsing: Ruby source code → Abstract Syntax Tree (AST)
- Compilation: AST → YARV bytecode instructions
- Execution: YARV VM executes bytecode
- 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