Benchmarking Your Ruby Code

22 Jul 2013 · By Casey Scarborough

There will more than likely come a time where you'll want to benchmark a piece of code that you've written, or you'd like to make it faster. Ruby comes packed with a module in its standard library that is built for benchmarking your code. The module is intuitively named Benchmark, and I'm going to go through a few examples of using it.

The first and simplest way to benchmark a piece of Ruby code is to use the Benchmark#measure method.

require 'benchmark'

puts Benchmark.measure { 50_000.downto(1).inject(:*) }

This line will determine the amount of time to calculate 50,000 factorial on your machine. After running this, you should get an output similar to the following:

1.690000   0.020000   1.710000 (  1.707248)

This shows the amount of user CPU time, system CPU time, the sum of the user and system CPU times, and the elapsed real time in seconds. Generally, you'll want to take a look at the far right number, as this shows the amount of time taken to perform the operation.

Benchmarking with Reports

Using the Benchmark#measure method is good for a quick benchmark on a simple piece of code, but typically you'll want to run multiple tests and compare the results. You can do this using the Benchmark#bm method.

require 'benchmark'

Benchmark.bm do |x|
  x.report { (1..6).reduce(:*) }
  x.report { 1.upto(6).inject(:*) }
  x.report { 6.downto(1).inject(:*) }
end

This will compare the multiple ways to calculate 50,000 factorial. You should receive an output similar to the following:

       user     system      total        real
   1.110000   0.010000   1.120000 (  1.119566)
   1.050000   0.000000   1.050000 (  1.061835)
   1.150000   0.010000   1.160000 (  1.148309)

You can also create labels with the Benchmark#bm method to create a more readable output.

require 'benchmark'

Benchmark.bm(7) do |x|
  x.report('range:')  { (1..6).reduce(:*) }
  x.report('upto:')   { 1.upto(6).inject(:*) }
  x.report('downto:') { 6.downto(1).inject(:*) }
end

This will give you the following format for your output:

              user     system      total        real
range:    1.100000   0.000000   1.100000 (  1.103561)
upto:     1.060000   0.010000   1.070000 (  1.061857)
downto:   1.160000   0.010000   1.170000 (  1.173509)

Combatting Garbage Collection

Your benchmarks will likely differ for each run due to memory allocation costs and garbage collection. To combat this, the Benchmark library contains a method called bmbm which runs the test multiple times. Using the example from the documentation, you can see the differences between the benchmarks:

require 'benchmark'

# Create an array of 1,000,000 random digits
array = (1..1000000).map { rand }

Benchmark.bmbm do |x|
  # Output the benchmark for sorting (destructive and non-destructive) the array
  x.report("sort!") { array.dup.sort! }
  x.report("sort")  { array.dup.sort  }
end

Your result should be similar to the following:

Rehearsal -----------------------------------------
sort!   1.090000   0.000000   1.090000 (  1.098318)
sort    1.090000   0.010000   1.100000 (  1.099114)
-------------------------------- total: 2.190000sec

            user     system      total        real
sort!   1.080000   0.000000   1.080000 (  1.082621)
sort    1.090000   0.000000   1.090000 (  1.084570)

This will give you a more accurate representation of your benchmarks.

Resources

Benchmark Documentation


comments powered by Disqus