Episode ini membahas optimasi performa Ruby: mengaktifkan YJIT dengan flag --yjit dan --yjit-mem-size, mengukur kecepatan dengan Benchmark, memprofil dengan ruby-prof, serta memahami GC.stat, GC.config, dan allocation hotspots untuk menemukan penyebab aplikasi lambat.

Aplikasi yang aman dan benar belum cukup — di production, kecepatan menentukan biaya server dan pengalaman pengguna. Episode 17 membahas optimasi performa Ruby: mengaktifkan YJIT, mengukur kecepatan dengan Benchmark, memprofil dengan ruby-prof, dan memahami garbage collection.
Banyak aplikasi lambat bukan karena Ruby lambat, melainkan karena kodenya mengalokasikan objek berlebihan atau melewatkan JIT. Kita mulai dari langkah termudah yang berdampak besar: menyalakan YJIT. Sepanjang episode, selalu kaitkan setiap keputusan dengan angka hasil pengukuran.
YJIT adalah Just-in-Time compiler production-ready untuk CRuby. Tanpa mengubah kode sama sekali, kalian bisa mendapat percepatan signifikan hanya dengan menambahkan flag saat menjalankan Ruby:
ruby --yjit app.rb
ruby --yjit --yjit-mem-size=64 app.rbruby --yjit app.rb mengaktifkan compiler di dalam proses. --yjit-mem-size membatasi memori yang dipakai kode hasil kompilasi, default 128 MiB. Untuk Rails, aktifkan lewat environment variable RUBY_YJIT_ENABLE=1 atau di config/production.rb. Setelah aktif, pastikan proses benar-benar memakainya dengan RubyVM::YJIT.enabled?.
YJIT menyediakan statistik internal. Aktifkan statistik dengan environment variable RUBY_YJIT_STATS=1, lalu baca dari kode:
puts RubyVM::YJIT.runtime_statsRubyVM::YJIT.runtime_stats mengembalikan hash berisi metrik seperti jumlah code yang dikompilasi, jumlah side-exit, dan rasio kompilasi. Bandingkan statistik sebelum dan sesudah optimasi kode — fokus pada side-exit yang tinggi karena menandakan kode yang sering dideoptimasi. Side-exit berlebihan biasanya berasal dari method yang sering berganti tipe argumen.
Sebelum mengoptimasi, ukur dulu. Benchmark membandingkan implementasi dan memberi angka yang bisa dijadikan dasar keputusan:
require "benchmark"
time = Benchmark.realtime do
1_000_000.times { "ruby".upcase }
end
puts "waktu: #{time} detik"Benchmark.realtime mengembalikan detik yang dihabiskan block. Untuk membandingkan beberapa pendekatan sekaligus, gunakan Benchmark.bm yang mencetak tabel per implementasi:
require "benchmark"
Benchmark.bm do |x|
x.report("upcase") { 1_000_000.times { "ruby".upcase } }
x.report("capitalize") { 1_000_000.times { "ruby".capitalize } }
endJangan mengoptimasi tanpa baseline — angka yang diukur selalu mengalahkan intuisi. Ulangi pengukuran beberapa kali karena CPU bisa berfluktuasi, lalu ambil nilai median.
Benchmark mengukur waktu keseluruhan, tetapi tidak memberi tahu bagian mana yang lambat. Di situlah ruby-prof berperan: ia memprofil setiap method call dan menyusun laporan:
gem install ruby-prof
ruby-prof -m --printer=call_stack app.rbruby-prof -m --printer=call_stack app.rb menulis profil dengan mode wall-time dan mencetak call stack. Cari method dengan self time terbesar — di sanalah allocasi dan perhitungan menumpuk. ruby-prof menghasilkan berbagai format, termasuk graph dan flat untuk dianalisis, serta bisa diintegrasikan dengan Rails lewat rack middleware.
Ruby membersihkan objek tak terpakai lewat garbage collection. Pantau aktivitasnya dengan GC.stat, dan sesuaikan perilakunya dengan GC.config:
puts GC.stat[:count]
GC.config(major_gc_interval: 180)GC.stat[:count] menunjukkan berapa kali GC berjalan sejak proses dimulai. GC.config(major_gc_interval: 180) mengatur interval major GC dalam detik — berguna untuk mengurangi freeze pada aplikasi latency-sensitive. Environment variable RUBY_GC_HEAP_INIT_SLOTS dan RUBY_GC_HEAP_GROWTH_FACTOR juga bisa diset dari luar untuk mengontrol kecepatan pertumbuhan heap.
Allocation hotspots adalah titik di mana kode membuat objek sementara berlebihan — penyebab GC terlalu sering jalan. ObjectSpace membantu mengukur:
puts ObjectSpace.count_objects[:T_STRING]ObjectSpace.count_objects[:T_STRING] menghitung objek String yang hidup saat itu. Naik turun drastis menandakan allocasi berlebihan di loop atau di hot path request. Pola umum pengurangan: hindari membuat array per iterasi, gunakan literal beku, dan pindahkan pembuatan objek ke luar loop. Pantau juga pertumbuhan RSS proses dari waktu ke waktu untuk melihat kebocoran memori.
Warning
Optimasi adalah ilmu pengukuran. Tanpa profil, kalian hanya menebak. Ukur dulu dengan Benchmark dan ruby-prof, ubah satu hal pada satu waktu, lalu ukur lagi.
Inti yang harus dibawa pulang:
--yjit dan memberi percepatan tanpa ubah kode.--yjit-mem-size membatasi memori kode terkompilasi, default 128 MiB.RubyVM::YJIT.runtime_stats mengungkap sisi yang sering dideoptimasi.GC.stat dan GC.config memantau serta menyesuaikan perilaku GC.Di episode 18 kita menambah ketelitian: type system Ruby dengan RBS dan Steep untuk static type checking, serta Sorbet dengan gradual typing dan sorbet-typed. Setelah performa, giliran keandalan tipe data yang dijamin kompiler.