Skip to content

Instantly share code, notes, and snippets.

@JuanitoFatas
Last active August 29, 2015 14:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JuanitoFatas/0ab17f51e1f43654e671 to your computer and use it in GitHub Desktop.
Save JuanitoFatas/0ab17f51e1f43654e671 to your computer and use it in GitHub Desktop.
to_proc hack vs built-in
require 'benchmark/ips'

class Array
  def to_proc
    ->(h) { length == 1 ? h[first] : h.values_at(*self) }
  end
end

LANGUAGE_AND_AGE = [
  { name: "Ruby", "age" => 21 }, { name: "Common Lisp", "age" => 56 }
]

def built_in
  LANGUAGE_AND_AGE.map { |h| [h[:name], h['age']] }
end

def to_proc_hack
  LANGUAGE_AND_AGE.map(&[:name, "age"])
end

Benchmark.ips do |x|
  x.report('built_in')     { built_in     }
  x.report('to_proc_hack') { to_proc_hack }
  x.compare!
end
$ ruby -v to-proc-hack.rb
ruby 2.2.0preview1 (2014-09-17 trunk 47616) [x86_64-darwin13]

Calculating -------------------------------------
            built_in     88340 i/100ms
        to_proc_hack     47669 i/100ms
-------------------------------------------------
            built_in  1806140.9 (±6.2%) i/s -    9010680 in   5.009917s
        to_proc_hack   665738.9 (±4.9%) i/s -    3336830 in   5.024990s

Comparison:
            built_in:  1806140.9 i/s
        to_proc_hack:   665738.9 i/s - 2.71x slower

Source: Array#to_proc for hash access - The Pug Automatic

@chastell
Copy link

For me (Ruby 2.1.5, i7-3517U 1.90GHz) the numbers are a bit closer:

Calculating -------------------------------------
            built_in    72.937k i/100ms
        to_proc_hack    43.146k i/100ms
-------------------------------------------------
            built_in      1.381M (± 0.8%) i/s -      6.929M
        to_proc_hack    673.754k (± 1.1%) i/s -      3.409M

Comparison:
            built_in:  1380622.2 i/s
        to_proc_hack:   673754.3 i/s - 2.05x slower

And much closer once I made LANGUAGE_AND_AGE 10,000 times larger:

Calculating -------------------------------------
            built_in    14.000  i/100ms
        to_proc_hack    14.000  i/100ms
-------------------------------------------------
            built_in    147.267  (± 1.4%) i/s -    742.000 
        to_proc_hack    153.800  (± 1.3%) i/s -    770.000 

Comparison:
        to_proc_hack:      153.8 i/s
            built_in:      147.3 i/s - 1.04x slower

I don’t think this kind of speed difference matters in practice (and if it does, it manifests itself with larger collections of hashes, when the penalty is much smaller). :)

@chastell
Copy link

Oh, OK, they’re larger with Ruby 2.2.0preview1 (yay, language speed-ups!).

For the two-element LANGUAGE_AND_AGE collection:

Calculating -------------------------------------
            built_in    77.065k i/100ms
        to_proc_hack    45.008k i/100ms
-------------------------------------------------
            built_in      1.770M (± 0.8%) i/s -      8.862M
        to_proc_hack    736.151k (± 1.2%) i/s -      3.691M

Comparison:
            built_in:  1770300.8 i/s
        to_proc_hack:   736151.5 i/s - 2.40x slower

For the 20,000-element LANGUAGE_AND_AGE collection:

Calculating -------------------------------------
            built_in    28.000  i/100ms
        to_proc_hack    18.000  i/100ms
-------------------------------------------------
            built_in    279.444  (± 0.7%) i/s -      1.400k
        to_proc_hack    195.625  (± 1.0%) i/s -    990.000 

Comparison:
            built_in:      279.4 i/s
        to_proc_hack:      195.6 i/s - 1.43x slower

@chastell
Copy link

Also (and I’ll shut up now):

require 'benchmark/ips'

class Array
  def to_proc
    ->(h) { length == 1 ? h[first] : h.values_at(*self) }
  end
end

Language = Struct.new(:name, :age)

LANGUAGE_AND_AGE = [
  { name: "Ruby", "age" => 21 }, { name: "Common Lisp", "age" => 56 }
]#* 10_000

STRUCTS = [
  Language.new("Ruby", 21), Language.new("Common Lisp", 56)
]#* 10_000

def built_in
  LANGUAGE_AND_AGE.map { |h| [h[:name]] }
end

def to_proc_hack
  LANGUAGE_AND_AGE.map(&[:name])
end

def structs
  STRUCTS.map(&:name)
end

Benchmark.ips do |x|
  x.report('built_in')     { built_in     }
  x.report('to_proc_hack') { to_proc_hack }
  x.report('structs')      { structs      }
  x.compare!
end

For two-element collections:

Calculating -------------------------------------
            built_in    78.254k i/100ms
        to_proc_hack    53.504k i/100ms
             structs    85.331k i/100ms
-------------------------------------------------
            built_in      2.094M (± 1.5%) i/s -     10.486M
        to_proc_hack    941.710k (± 1.5%) i/s -      4.708M
             structs      2.295M (± 1.7%) i/s -     11.520M

Comparison:
             structs:  2295166.7 i/s
            built_in:  2093725.2 i/s - 1.10x slower
        to_proc_hack:   941710.2 i/s - 2.44x slower

For 20,000-element collections:

Calculating -------------------------------------
            built_in    35.000  i/100ms
        to_proc_hack    35.000  i/100ms
             structs    78.000  i/100ms
-------------------------------------------------
            built_in    346.064  (± 3.5%) i/s -      1.750k
        to_proc_hack    362.379  (± 3.3%) i/s -      1.820k
             structs    795.057  (± 1.1%) i/s -      3.978k

Comparison:
             structs:      795.1 i/s
        to_proc_hack:      362.4 i/s - 2.19x slower
            built_in:      346.1 i/s - 2.30x slower

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment