Benchmarking & Profiling
Benchmarking is measuring how long code takes so you can compare approaches with evidence, using Ruby's built-in Benchmark library instead of guessing which version is faster.
Learn Benchmarking & Profiling in our free Ruby course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a quick reference.
Part of the free Ruby course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
In this lesson you'll measure fairly with bm , bmbm , and realtime — and learn to report the winner, not machine-specific seconds.
What You'll Learn in This Lesson
1️⃣ Benchmark.realtime: Time a Block
The simplest tool is Benchmark.realtime , which returns the elapsed wall-clock seconds for a block. Wrap each approach, compare the two numbers, and print the conclusion — never the raw seconds, since those depend on the machine. Here we prove that appending with << beats rebuilding strings with + .
2️⃣ Benchmark.bm: Labelled Comparisons
Benchmark.bm prints a tidy table of labelled blocks and returns their measurements. We capture each report result, compare the .real times, and announce the winner. Here, appending to an array with << beats building a fresh array with + each step.
3️⃣ bmbm, Frozen Strings & Allocation
Benchmark.bmbm runs a rehearsal pass first so warm-up and garbage-collection effects don't bias the result — the fairest comparison. We use it to show that a frozen string literal, reused as one object, beats allocating a fresh String on every call. The lesson underneath: fewer allocations, faster code.
🎯 Your Turn
Measure symbol-key vs string-key hash lookups. Replace the ___ with the Benchmark method that returns elapsed seconds for a block.
Use Benchmark.realtime to compare a manual each accumulation against the built-in sum , and print which wins. Run with ruby bench.rb .
📋 Quick Reference — Benchmarking
Practice quiz
Which require do you need before using the Benchmark library?
- require 'time'
- require 'bench'
- require 'benchmark'
- No require is needed
Answer: require 'benchmark'. Benchmark lives in the standard library; load it with require 'benchmark'.
What does Benchmark.realtime { ... } return?
- The elapsed wall-clock seconds (a Float) for the block
- A labelled timing table
- The number of iterations
- Nothing — it only prints
Answer: The elapsed wall-clock seconds (a Float) for the block. Benchmark.realtime returns the elapsed seconds for the block as a Float.
What is the key difference between Benchmark.bm and Benchmark.bmbm?
- bm is for arrays, bmbm is for strings
- bmbm prints nothing
- There is no difference
- bmbm runs a rehearsal pass first to reduce warm-up/GC bias
Answer: bmbm runs a rehearsal pass first to reduce warm-up/GC bias. bmbm runs every block once as a rehearsal before the measured pass, giving a fairer comparison.
When reporting benchmark results, what should you publish?
- The exact seconds, e.g. '0.03s'
- The relative winner — which approach is consistently faster
- The machine's CPU model
- The iteration count only
Answer: The relative winner — which approach is consistently faster. Raw seconds depend on the machine; the reproducible conclusion is which approach wins.
Why are frozen string literals often faster in hot loops?
- Ruby reuses one immutable object instead of allocating a new String each time
- They run on a separate thread
- They skip the garbage collector entirely forever
- They are compiled to machine code
Answer: Ruby reuses one immutable object instead of allocating a new String each time. Freezing lets Ruby reuse a single object, cutting allocations and GC work.
In a hot loop, appending in place with << usually beats building a new array with + because:
- + is a syntax error on arrays
- << sorts as it goes
- + allocates a brand-new array every iteration, creating GC pressure
- << uses less memory by compressing data
Answer: + allocates a brand-new array every iteration, creating GC pressure. a = a + [x] builds a new array each step; a << x mutates in place with far fewer allocations.
Given two Benchmark report results r1 and r2, how do you read each one's measured time?
- r1.seconds
- r1.real
- r1.time
- r1.elapsed
Answer: r1.real. A report result exposes .real (and .user/.system/.total) — compare r1.real < r2.real.
Why shouldn't you trust a single timing measurement?
- Ruby caches all results
- Single runs are always too fast
- The first run is always the fastest
- Noise from GC, CPU scaling, and background work makes one run unreliable
Answer: Noise from GC, CPU scaling, and background work makes one run unreliable. Background processes and especially GC make a lone run noisy; run many iterations and compare.
How do you freeze every string literal in a whole file at once?
- Call freeze on each string
- Add the magic comment # frozen_string_literal: true as the first line
- Run with ruby --freeze
- Wrap the file in a freeze block
Answer: Add the magic comment # frozen_string_literal: true as the first line. The magic comment on line one freezes all string literals in that file.
What is the main reason heavy object allocation in a loop hurts performance?
- It uses more disk space
- It disables the JIT permanently
- Each object must be tracked and freed by the garbage collector, costing time
- Allocation always blocks the network
Answer: Each object must be tracked and freed by the garbage collector, costing time. Every allocated object eventually burdens the GC, so fewer throwaway objects means faster code.