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.