Simple Ruby Speed Benchmarks


The Blog of Tom Webster

  2015-11-30 04:14:00 PST

To show the speed differences in some ruby applications, I wrote a simple ceaser cipher in a few different ways, then timed how long they took to run. I thought the results were interesting enough to post here.

Big thanks to Alan Skorkin for the simple timing code.

The first way creates a string of A-Z, then add the lower case letters to it, then split each character out, making an array. This implementation isn't a one-line array creation like the second.

The second way uses a string that's already created with A-Za-z, and splits them into an array.

The third way uses a hash of a-z, then creates the mappings for A-Za-z.

The fourth way uses a full hash of A-Za-z.

chart_with_upwards_trend Let's post some graphs! chart_with_upwards_trend

I removed the outliers from the four trials and made some graphs. First up, the average for 92 trials:

As you can see, it looks like the full hash implementation is the clear winner here.

Next up is the 92 trials plotted out:

Here's the code if you would like to run your own analysis.

#!/usr/bin/env ruby

# There are lots of ways to do things in ruby. Some are faster than others.
# This ruby script outputs benchmarks in CSV format
# Thanks to Alan Skorkin for the simple timing code
#   http://www.skorks.com/2010/03/timing-ruby-code-it-is-easy-with-benchmark/

require "benchmark"

print "HalfArray,FullArray,HalfHash,FullHash\n"

100.times do
  # Half array manipulation
  def ceaser_cipher(string)
    alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    alphabet += alphabet.downcase
    alphabet = alphabet.split(//)
    converted_string_array = []
    string.split(//).each do |l|
      if alphabet.include?(l)
        converted_string_array.push(alphabet[(alphabet.find_index(l) - 51).abs].swapcase)
      else
        converted_string_array.push(l)
      end
    end
    return converted_string_array.join
  end

  time = Benchmark.realtime do
    ceaser_cipher("ABC abc")
  end
  print (time * 1000)
  print ","

  # Full array manipulation
  def ceaser_cipher(string)
    alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".split(//)
    converted_string_array = []
    string.split(//).each do |l|
      if alphabet.include?(l)
        converted_string_array.push(alphabet[(alphabet.find_index(l) - 51).abs].swapcase)
      else
        converted_string_array.push(l)
      end
    end
    return converted_string_array.join
  end

  time = Benchmark.realtime do
    ceaser_cipher("ABC abc")
  end
  print (time * 1000)
  print ","

  # Half hash dictionary
  def ceaser_cipher(string)
    initial_dictionary = Hash[
      "a" => "z",
      "b" => "y",
      "c" => "x",
      "d" => "w",
      "e" => "v",
      "f" => "u",
      "g" => "t",
      "h" => "s",
      "i" => "r",
      "j" => "q",
      "k" => "p",
      "l" => "o",
      "m" => "n",
      "n" => "m",
      "o" => "l",
      "p" => "k",
      "q" => "j",
      "r" => "i",
      "s" => "h",
      "t" => "g",
      "u" => "f",
      "v" => "e",
      "w" => "d",
      "x" => "c",
      "y" => "b",
      "z" => "a"
    ]
    dictionary = {}
    initial_dictionary.each do |original, reversed|
      dictionary[original] = reversed
      dictionary[original.upcase] = reversed.upcase
    end
    converted_string_array = []
    string.split(//).each do |l|
      if dictionary.include?(l)
        converted_string_array.push(dictionary[l])
      else
        converted_string_array.push(l)
      end
    end
    return converted_string_array.join
  end

  time = Benchmark.realtime do
    ceaser_cipher("ABC abc")
  end
  print (time * 1000)
  print ","

  # Full hash dictionary
  def ceaser_cipher(string)
     dictionary = Hash[
      "A" => "Z",
      "B" => "Y",
      "C" => "X",
      "D" => "W",
      "E" => "V",
      "F" => "U",
      "G" => "T",
      "H" => "S",
      "I" => "R",
      "J" => "Q",
      "K" => "P",
      "L" => "O",
      "M" => "N",
      "N" => "M",
      "O" => "L",
      "P" => "K",
      "Q" => "J",
      "R" => "I",
      "S" => "H",
      "T" => "G",
      "U" => "F",
      "V" => "E",
      "W" => "D",
      "X" => "C",
      "Y" => "B",
      "Z" => "A",
      "a" => "z",
      "b" => "y",
      "c" => "x",
      "d" => "w",
      "e" => "v",
      "f" => "u",
      "g" => "t",
      "h" => "s",
      "i" => "r",
      "j" => "q",
      "k" => "p",
      "l" => "o",
      "m" => "n",
      "n" => "m",
      "o" => "l",
      "p" => "k",
      "q" => "j",
      "r" => "i",
      "s" => "h",
      "t" => "g",
      "u" => "f",
      "v" => "e",
      "w" => "d",
      "x" => "c",
      "y" => "b",
      "z" => "a"
    ]
    converted_string_array = []
    string.split(//).each do |l|
      if dictionary.include?(l)
        converted_string_array.push(dictionary[l])
      else
        converted_string_array.push(l)
      end
    end
    return converted_string_array.join
  end

  time = Benchmark.realtime do
    ceaser_cipher("ABC abc")
  end
  print (time * 1000)
  print "\n"
end

I'm not entirely familiar with low low level ruby, so I can't offer any grand insights, but I thought it was some decent code and a nice set of graphs.