Belajar Ruby - Performance Optimization & Profiling
Series/Belajar Ruby/Episode 17
Episode 17 of 23

Belajar Ruby - Performance Optimization & Profiling

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.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Pendahuluan

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.

Mengaktifkan YJIT

Flag --yjit

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:

Menjalankan Ruby dengan YJIT
ruby --yjit app.rb
ruby --yjit --yjit-mem-size=64 app.rb

ruby --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?.

Mengukur dengan RubyVM::YJIT.runtime_stats

YJIT menyediakan statistik internal. Aktifkan statistik dengan environment variable RUBY_YJIT_STATS=1, lalu baca dari kode:

RubyMembaca statistik YJIT
puts RubyVM::YJIT.runtime_stats

RubyVM::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.

Benchmark dan Profiling

Benchmark

Sebelum mengoptimasi, ukur dulu. Benchmark membandingkan implementasi dan memberi angka yang bisa dijadikan dasar keputusan:

RubyBenchmark sederhana
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:

RubyMembandingkan beberapa 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 } }
end

Jangan mengoptimasi tanpa baseline — angka yang diukur selalu mengalahkan intuisi. Ulangi pengukuran beberapa kali karena CPU bisa berfluktuasi, lalu ambil nilai median.

ruby-prof

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:

Menjalankan ruby-prof
gem install ruby-prof
ruby-prof -m --printer=call_stack app.rb

ruby-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.

Memahami GC dan Memori

GC.stat dan GC.config

Ruby membersihkan objek tak terpakai lewat garbage collection. Pantau aktivitasnya dengan GC.stat, dan sesuaikan perilakunya dengan GC.config:

RubyMemantau dan mengonfigurasi GC
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.

ObjectSpace dan Allocation Hotspots

Allocation hotspots adalah titik di mana kode membuat objek sementara berlebihan — penyebab GC terlalu sering jalan. ObjectSpace membantu mengukur:

RubyMenghitung allocasi string
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.

Penutup

Inti yang harus dibawa pulang:

  • YJIT aktif hanya dengan --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.
  • Benchmark memberikan baseline waktu yang menjadi dasar keputusan.
  • ruby-prof menemukan method dengan self time terbesar.
  • GC.stat dan GC.config memantau serta menyesuaikan perilaku GC.
  • Allocation hotspots di object besar adalah penyebab GC berlebihan.

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.

Belajar Ruby - Performance Optimization & Profiling | Belajar Ruby