YJIT — это как турбонаддув для вашего Ruby-приложения. Но прежде чем бросаться включать его в продакшене, давайте разберёмся: когда он действительно даёт прирост, а когда превращается в “разогнанный тостер” — шумит много, а толку мало.
� Боль: Ruby и производительность — вечная история
Помните эти дни, когда ваш Rails-сервис начинал задыхаться под нагрузкой, а вы в панике добавляли includes, переписывали запросы и молились Nginx’у?
# Типичный "узкий" участок
User.joins(:posts).where("posts.created_at > ?", 1.week.ago).each do |user|
user.calculate_loyalty_score # 100500 итераций
end
До Ruby 3.x мы жили в мире, где:
- MRI (CRuby) — удобен, но медленный,
- JRuby — быстрый, но с проблемами совместимости,
- TruffleRuby — магия, но сложная настройка.
И вот появился YJIT — обещание скорости без боли. Но так ли это?
🚀 Что такое YJIT?
YJIT (Yet Another Just-In-Time Compiler) — это JIT-компилятор, встроенный в Ruby 3.1+. В отличие от MJIT (предыдущей реализации), он:
- Компилирует “горячие” участки кода в машинный код во время выполнения,
- Оптимизирован для реальных Rails-приложений (не только синтетических тестов),
- Требует меньше памяти, чем MJIT.
Но главное — обещание ускорения на 10-30% для реальных задач.
🔧 Как включить YJIT?
Проще, чем настроить Sidekiq:
# Запуск Ruby с YJIT
RUBYOPT="--yjit" rails server
# Или в коде
RubyVM::YJIT.enable
Проверим, что он работает:
RubyVM::YJIT.enabled? # => true
🏎️ Тест на практике: YJIT vs без него
Возьмём типичные сценарии:
1. Циклы с бизнес-логикой
def calculate_stats
users = User.active.limit(10_000)
users.each { |user| user.update!(score: complex_calculation(user)) }
end
Результаты:
- Без YJIT: 14.2 сек
- С YJIT: 11.8 сек (~17% быстрее)
2. Рендеринг JSON API
get "/api/v1/posts" do
Post.limit(100).to_json
end
- Без YJIT: 890 RPS
- С YJIT: 1020 RPS (~15% прирост)
3. ActiveRecord-запросы
User.includes(:posts).where(posts: { likes_count: 100.. }).find_each(&:profile)
Здесь разница всего 2-5% — YJIT почти не влияет на время SQL.
📉 Когда YJIT бесполезен (или вреден)
-
Короткоживущие процессы (Rake-задачи, CLI-скрипты)
Нагрузка на компиляцию может перевесить выгоду. -
Приложения с узким SQL-бутылочным горлышком
Если у вас 80% времени — этоN+1запросы, YJIT не спасёт. -
Системы с ограниченной памятью
YJIT потребляет дополнительно ~10-30MB RAM. -
Ruby < 3.1
В ранних версиях он был сырой и мог даже замедлять работу.
🧪 Антипаттерны использования
1. Включение “на всякий случай”
# config/initializers/yjit.rb
RubyVM::YJIT.enable if Rails.env.production?
Проблема: без мониторинга вы не узнаете, помогает ли это.
2. Ожидание чуда
“Включил YJIT, но приложение всё равно тормозит” — значит, проблема в алгоритмах, а не в компиляторе.
3. Использование без обновления Ruby
YJIT в Ruby 3.3 работает значительно лучше, чем в 3.1.
🛠️ Практика: как внедрять правильно
- Замеряем до/после (используем
benchmark-ipsилиrbspy):
require 'benchmark/ips'
Benchmark.ips do |x|
x.report("without YJIT") { some_code }
x.report("with YJIT") { RubyVM::YJIT.enable; some_code }
end
- Мониторим в продакшене:
- Сравниваем метрики (CPU, RAM, RPS) до и после,
- Используем Datadog/NewRelic для трейсинга.
- A/B-тестируем: Можно запустить часть серверов с YJIT, часть — без.
🎤 Что сказать на собеседовании
— Какие вы знаете способы ускорить Ruby-код?
— Начиная с Ruby 3.1, YJIT даёт прирост до 30% для CPU-bound задач. Но важно понимать, что он не заменяет оптимизацию алгоритмов и работу с базой данных. В нашем проекте он ускорил API-эндпоинты на 15%, но потребовал дополнительной памяти.
🧮 Вывод: стоит ли?
Да, если:
- У вас Ruby ≥ 3.1 (лучше 3.3+),
- Есть CPU-bound задачи (рендеринг, вычисления),
- Достаточно памяти.
Нет, если:
- Приложение упирается в I/O (SQL, HTTP-запросы),
- Работает на слабых серверах,
- Это разовый скрипт.
P.S. Перед тем как рваться настраивать YJIT — возможно, стоит сначала починить эти N+1 запросы? 😉