Ruby 3.4.4 with YJIT and jemalloc: A Performance Deep Dive

July 16, 2025 • By Vladimir Elchinov

*How Rails Blueprint achieves 15-30% performance improvements and 10-20% memory savings with Ruby optimization*




Ruby performance has come a long way since the early days of Rails. With Ruby 3.4.4, we now have access to production-ready JIT compilation and advanced memory management that can significantly boost your Rails application's performance. At Rails Blueprint, we've implemented these optimizations from day one, and the results speak for themselves.

## The Performance Problem

Standard Ruby installations, while developer-friendly, leave performance gains on the table. Most Rails applications run with:
- **No JIT compilation** - Missing 15-30% performance improvements
- **Default memory allocator** - Higher memory usage and fragmentation
- **Suboptimal garbage collection** - More frequent GC pauses

Rails Blueprint addresses all of these issues with a carefully configured Ruby setup.

## YJIT: The Game-Changing JIT Compiler

### What is YJIT?

YJIT (Yet Another Ruby JIT) is Ruby's production-ready Just-In-Time compiler, introduced in Ruby 3.1 and significantly improved in 3.4. Unlike previous JIT implementations, YJIT:

- **Compiles hot code paths** to native machine code
- **Optimizes incrementally** - only the code that needs it
- **Maintains Ruby semantics** - no behavioral changes
- **Delivers consistent gains** - 15-30% performance improvement

### Real-World Impact

In Rails Blueprint applications, YJIT shines in:

**Command Objects**: Our heavy use of command objects in `app/commands/` with dry-validation benefits significantly from JIT compilation of repeated business logic.

**View Components**: Component-based views with the view_component gem see faster rendering through optimized template compilation.

**Background Jobs**: Good Job processing gets a boost from JIT-compiled job execution paths.

### Enabling YJIT in Production

Rails Blueprint automatically enables YJIT in production through environment configuration:

```ruby
# config/environments/production.rb
# YJIT is enabled via RUBY_YJIT_ENABLE=1 environment variable
# This is set in our deployment scripts and Docker configuration
```

You can verify YJIT is running:

```bash
ruby --yjit -e "puts RubyVM::YJIT.enabled?"
# => true
```

## jemalloc: Superior Memory Management

### Why jemalloc?

Ruby's default memory allocator (typically glibc malloc) is general-purpose but not optimized for Ruby's allocation patterns. jemalloc provides:

- **10-20% memory usage reduction**
- **Better cache locality** for improved performance
- **Reduced memory fragmentation**
- **More predictable allocation patterns**

### Memory Allocation Patterns in Rails

Rails applications have specific memory patterns that jemalloc handles better:

**Small object allocation**: Ruby creates many small objects (strings, arrays, hashes) that jemalloc manages more efficiently.

**Garbage collection**: jemalloc's design reduces GC pressure by minimizing fragmentation.

**Request handling**: Per-request memory allocation and deallocation patterns benefit from jemalloc's optimizations.

## Installation Guide

### macOS Installation

```bash
# Install jemalloc
brew install jemalloc

# Install rust (required for YJIT)
brew install rust

# Install Ruby with optimizations
RUBY_CONFIGURE_OPTS="--enable-yjit --with-jemalloc=$(brew --prefix jemalloc)" rbenv install 3.4.4
```

### Ubuntu/Debian Installation

```bash
# Install dependencies
sudo apt-get update
sudo apt-get install -y libjemalloc-dev rustc

# Install Ruby with optimizations
RUBY_CONFIGURE_OPTS="--enable-yjit --with-jemalloc" rbenv install 3.4.4
```

### Verification

**Check Ruby Version:**
```bash
ruby --version
# Expected: ruby 3.4.4 (2025-05-14 revision a38531fd3f) +PRISM [x86_64-...]
```

**Verify YJIT is Available:**
```bash
ruby --yjit -e "puts RubyVM::YJIT.enabled?"
# Expected: true
```

**Verify jemalloc is Linked (Linux):**
```bash
ldd $(rbenv which ruby) | grep jemalloc
# Expected: libjemalloc.so.2 => /lib/.../libjemalloc.so.2
```

**Verify jemalloc is Linked (macOS):**
```bash
otool -L $(rbenv which ruby) | grep jemalloc
# Expected: jemalloc library path
```

## Production Configuration

### Docker Setup

Rails Blueprint includes optimized Docker configuration:

```dockerfile
# Install jemalloc in production container
RUN apt-get install --no-install-recommends -y curl libjemalloc2 libsqlite3-0 libvips

# Entrypoint script automatically enables jemalloc
#!/bin/bash -e
# Enable jemalloc for reduced memory usage and latency.
if [ -z "${LD_PRELOAD+x}" ] && [ -f /usr/lib/*/libjemalloc.so.2 ]; then
  export LD_PRELOAD="$(echo /usr/lib/*/libjemalloc.so.2)"
fi
```

### Environment Variables

```bash
# Enable YJIT
RUBY_YJIT_ENABLE=1

# jemalloc is loaded via LD_PRELOAD
# This is handled automatically by the entrypoint script
```

## Performance Benchmarking

### Measuring the Impact

Here's how to benchmark your Rails Blueprint application:

```ruby
# Create a benchmark script: script/performance_test.rb
require 'benchmark'

def simulate_request_cycle
  # Simulate typical Rails Blueprint request:
  # 1. Authentication check
  # 2. Command object execution
  # 3. View component rendering
  # 4. Background job enqueue

  user = User.find(1)
  result = SomeCommand.call(user: user, params: sample_params)
  MyComponent.new(data: result.data).render_in(view_context)
  SomeJob.perform_later(result.id)
end

# Benchmark with and without YJIT
Benchmark.bm do |x|
  x.report("without YJIT") { 1000.times { simulate_request_cycle } }
  # Enable YJIT and run again
  RubyVM::YJIT.enable
  x.report("with YJIT") { 1000.times { simulate_request_cycle } }
end
```

### Memory Usage Monitoring

```ruby
# Monitor memory usage
require 'objspace'

def memory_usage
  ObjectSpace.count_objects
end

# Track memory before and after jemalloc
puts "Memory objects: #{memory_usage[:TOTAL]}"
```

## Monitoring in Production

### NewRelic Integration

Rails Blueprint includes NewRelic configuration to monitor performance improvements:

```yaml
# config/newrelic.yml
production:
  app_name: <%= Rails.application.class.module_parent_name %>
  license_key: <%= Rails.application.credentials.newrelic_license_key %>

  # Track Ruby performance metrics
  ruby_vm_stats:
    enabled: true

  # Monitor garbage collection
  gc_profiler:
    enabled: true
```

### Health Endpoint

Monitor performance through the built-in health endpoint:

```bash
curl https://yourapp.com/health
```

```json
{
  "status": "ok",
  "version": {
    "basic": "1.2.0"
  },
  "git_revision": "c011f46f988ea5421454b3897e4b29c14a09861b",
  "timestamp": "2025-07-16T10:27:04Z"
}
```

## Common Issues and Solutions

### YJIT Compilation Issues

**Problem**: YJIT not available after installation
**Solution**: Ensure Rust is installed and `--enable-yjit` flag was used

```bash
# Check if YJIT is compiled in
ruby -e "puts RubyVM::YJIT.respond_to?(:enable)"
# Should return: true
```

### jemalloc Loading Issues

**Problem**: jemalloc not being loaded in production
**Solution**: Verify LD_PRELOAD is set correctly

```bash
# Check if jemalloc is loaded
lsof -p $$ | grep jemalloc
```

### Memory Leak Detection

**Problem**: Memory usage still growing despite jemalloc
**Solution**: Use memory profiling tools

```ruby
# Add to Gemfile for debugging
group :development do
  gem 'memory_profiler'
  gem 'rack-mini-profiler'
end
```

## Ruby 3.4 Compatibility

### Removed Standard Libraries

Ruby 3.4 removed some standard libraries. Rails Blueprint includes necessary gems:

```ruby
# Gemfile additions for Ruby 3.4 compatibility
gem 'csv'      # Removed from Ruby 3.4 stdlib
gem 'observer' # Removed from Ruby 3.4 stdlib
```

### Performance Improvements in 3.4

Ruby 3.4 includes additional performance improvements:
- **PRISM parser** - New default parser with better performance
- **Improved garbage collection** - More efficient object allocation
- **Better memory layout** - Optimized object structure

## Future Considerations

### Ruby 3.5 and Beyond

Keep an eye on upcoming Ruby versions:
- **Enhanced YJIT** - Continued JIT improvements
- **Better memory management** - Further allocation optimizations
- **Concurrency improvements** - Better thread and fiber performance

### Application-Specific Optimizations

Consider these Rails Blueprint-specific optimizations:

**Command Pattern**: Structure business logic to benefit from JIT compilation
**Component Architecture**: Use view components for better memory efficiency
**Background Jobs**: Optimize job processing with Good Job and PostgreSQL

## Conclusion

Ruby 3.4.4 with YJIT and jemalloc represents a significant performance leap for Rails applications. Rails Blueprint makes these optimizations accessible from day one, delivering:

- **15-30% performance improvements** through YJIT
- **10-20% memory usage reduction** with jemalloc
- **Production-ready configuration** out of the box
- **Monitoring and verification tools** included

The performance benefits compound over time as YJIT learns your application's hot paths and jemalloc optimizes memory allocation patterns. Combined with Rails Blueprint's modern architecture (Hotwire, ViewComponent, Good Job), you get a Rails application that's not just feature-complete but performance-optimized.

Ready to experience these performance improvements? Get started with [Rails Blueprint](https://railsblueprint.com) and feel the difference optimized Ruby can make.

---

*This article is part of our technical deep dive series. Next up: "PostgreSQL as Your Only Infrastructure: Why We Ditched Redis for Background Jobs"*

Start creating your next app now