Skip to content

Instantly share code, notes, and snippets.

@hawx
Created February 10, 2011 17:19
Show Gist options
  • Save hawx/820915 to your computer and use it in GitHub Desktop.
Save hawx/820915 to your computer and use it in GitHub Desktop.
Setting local variables within procs object, with benchmarks
require_relative 'local_in_procs'
f = lambda { message }
a = {:message => "Hello, world!"}
# Test the speed when using normal proc params
f_control = lambda {|a| a[:message] }
def rand_letters(n, l)
r = []
n.times do
a = ""
l.times do
a << ("a".."z").to_a[rand(26)]
end
r << a
end
r
end
def rands(n)
r = []
n.times do
r << rand
end
r
end
a_1 = {:message => "Hello, world!"}
a_10 = Hash[ rand_letters(9, 8).zip(rands(9)) << [:message, "Hello, world!"] ]
a_100 = Hash[ rand_letters(99, 8).zip(rands(99)) << [:message, "Hello, world!"] ]
a_1000 = Hash[ rand_letters(999, 8).zip(rands(999)) << [:message, "Hello, world!"] ]
n = 1000
as = [a_1, a_10, a_100, a_1000]
require 'benchmark'
Benchmark.bm(20) do |x|
as.each do |a|
puts "\na.size = #{a.size} ----------------------------------------------------------"
x.report " in a class:" do
n.times { in_class(a, f) }
end
x.report " multi-use class:" do
n.times { in_missing_class(a, f) }
end
x.report " multi-use object:" do
n.times { in_missing_object(a, f) }
end
x.report " control case:" do
n.times { f_control.call(a) }
end
x.report " control in class:" do
n.times { in_missing_class(a, f_control) }
end
x.report " control in object:" do
n.times { in_missing_object(a, f_control) }
end
GC.start # Make sure to run each run as though freshly started
end
end
# Here are three ways of running a proc with "local variables" set.
# Really they aren't local variables but functions, it seems it is
# impossible to set local variables or run procs with bindings which
# have the variables set. Trust me I've tried.
# METHOD 1: WITH A CLASS
# ----------------------
# Create a class then set methods for each arg, then run the proc in
# the newly created class.
#
def in_class(args, proc)
k = Class.new
args.each do |a,v|
k.send(:define_method, a) { v }
end
k.new.instance_exec &proc
end
# METHOD 2: USING METHOD MISSING
# ------------------------------
# Only create one class, which is then stored in a @var, this then
# takes the arguments and uses method missing to pass the correct
# value from the hash.
#
def in_missing_class(a, f)
if @k
if f.arity > 0
@k.new(a).instance_exec(a, &f)
else
@k.new(a).instance_exec(&f)
end
else
@k = Class.new {
def initialize(args)
@a = args
end
def method_missing(sym, *args)
if @a.has_key?(sym)
@a[sym]
else
super
end
end
}
in_missing_class(a, f)
end
end
# METHOD 3: USING METHOD MISSING AGAIN
# ------------------------------------
# This time create a single instance of a class which has a method
# allowing you to set the hash of args each time it is called, this
# way you aren't creating lots of different instances of a class.
#
def in_missing_object(a, f)
if @o
@o._run a, f
else
@o = Class.new {
def _run(a, f)
@a = a
if f.arity > 0
instance_exec(a, &f)
else
instance_exec &f
end
end
def method_missing(sym, *args)
if @a.has_key?(sym)
@a[sym]
else
super
end
end
}.new
end
end
a = {:name => "world"}
f = proc { puts "Hello, #{name}!" }
in_class(a, f)
#=> Hello, world!
in_missing_class(a, f)
#=> Hello, world!
in_missing_object(a, f)
#=> Hello, world!
require_relative 'local_in_procs'
f = lambda { message }
a = {:message => "Hello, world!"}
# Test the speed when using normal proc params
f_control = lambda {|a| a[:message] }
require 'benchmark'
ns = [10, 100, 1000, 10_000, 100_000]
Benchmark.bm(20) do |x|
ns.each do |n|
puts "\nn = #{n} ----------------------------------------------------------"
x.report " in a class:" do
n.times { in_class(a, f) }
end
x.report " multi-use class:" do
n.times { in_missing_class(a, f) }
end
x.report " multi-use object:" do
n.times { in_missing_object(a, f) }
end
x.report " control case:" do
n.times { f_control.call(a) }
end
x.report " control in class:" do
n.times { in_missing_class(a, f_control) }
end
x.report " control in object:" do
n.times { in_missing_object(a, f_control) }
end
GC.start # Make sure to run each run as though freshly started
end
end

Benchmarking Multiple Runs

> ruby n_bench.rb
                          user     system      total        real

n = 10 ----------------------------------------------------------
  in a class:         0.000000   0.000000   0.000000 (  0.000366)
  multi-use class:    0.000000   0.000000   0.000000 (  0.000181)
  multi-use object:   0.000000   0.000000   0.000000 (  0.000144)
  control case:       0.000000   0.000000   0.000000 (  0.000012)
  control in class:   0.000000   0.000000   0.000000 (  0.000139)
  control in object:  0.000000   0.000000   0.000000 (  0.000018)

n = 100 ----------------------------------------------------------
  in a class:         0.000000   0.000000   0.000000 (  0.001526)
  multi-use class:    0.000000   0.000000   0.000000 (  0.000717)
  multi-use object:   0.000000   0.000000   0.000000 (  0.000149)
  control case:       0.000000   0.000000   0.000000 (  0.000049)
  control in class:   0.000000   0.000000   0.000000 (  0.000268)
  control in object:  0.000000   0.000000   0.000000 (  0.000094)

n = 1000 ----------------------------------------------------------
  in a class:         0.030000   0.000000   0.030000 (  0.032788)
  multi-use class:    0.000000   0.000000   0.000000 (  0.003178)
  multi-use object:   0.010000   0.000000   0.010000 (  0.001427)
  control case:       0.000000   0.000000   0.000000 (  0.000394)
  control in class:   0.030000   0.000000   0.030000 (  0.035087)
  control in object:  0.000000   0.000000   0.000000 (  0.000831)

n = 10000 ----------------------------------------------------------
  in a class:         0.400000   0.010000   0.410000 (  0.409498)
  multi-use class:    0.150000   0.000000   0.150000 (  0.155968)
  multi-use object:   0.030000   0.000000   0.030000 (  0.026565)
  control case:       0.010000   0.000000   0.010000 (  0.003869)
  control in class:   0.070000   0.000000   0.070000 (  0.074177)
  control in object:  0.020000   0.000000   0.020000 (  0.020634)

n = 100000 ----------------------------------------------------------
  in a class:         4.450000   0.040000   4.490000 (  4.508097)
  multi-use class:    1.240000   0.000000   1.240000 (  1.242558)
  multi-use object:   0.140000   0.000000   0.140000 (  0.144972)
  control case:       0.040000   0.000000   0.040000 (  0.039157)
  control in class:   0.830000   0.000000   0.830000 (  0.837038)
  control in object:  0.100000   0.000000   0.100000 (  0.097637)
RANK:                                              n = 100000      n = 10
--------------------------------------------------------------------------
6. #in_class                                       11513%          3050%
5. #in_missing_class                               3173%           1508%
4. #in_missing_class CONTROL (except for n=10)     2138%           1158%
3. #in_missing_object                              370%            1200%
2. #in_missing_object CONTROL                      249%            150%
1. #call                                           100%            100%

So #in_missing_object is best case nearly 4 times slower than ordinary proc params and worst case around 12 times slower, and performs better when ran multiple times.

Benchmarking Multiple Arguments

> ruby a_bench.rb
                          user     system      total        real

a.size = 1 ----------------------------------------------------------
  in a class:         0.040000   0.000000   0.040000 (  0.044814)
  multi-use class:    0.010000   0.000000   0.010000 (  0.002935)
  multi-use object:   0.000000   0.000000   0.000000 (  0.001318)
  control case:       0.000000   0.000000   0.000000 (  0.000418)
  control in class:   0.010000   0.000000   0.010000 (  0.017274)
  control in object:  0.010000   0.000000   0.010000 (  0.000818)

a.size = 10 ----------------------------------------------------------
  in a class:         0.100000   0.010000   0.110000 (  0.102900)
  multi-use class:    0.000000   0.000000   0.000000 (  0.002817)
  multi-use object:   0.000000   0.000000   0.000000 (  0.001272)
  control case:       0.010000   0.000000   0.010000 (  0.000398)
  control in class:   0.000000   0.000000   0.000000 (  0.002198)
  control in object:  0.000000   0.000000   0.000000 (  0.000885)

a.size = 100 ----------------------------------------------------------
  in a class:         0.700000   0.020000   0.720000 (  0.723531)
  multi-use class:    0.000000   0.000000   0.000000 (  0.002969)
  multi-use object:   0.000000   0.000000   0.000000 (  0.001269)
  control case:       0.000000   0.000000   0.000000 (  0.000401)
  control in class:   0.010000   0.000000   0.010000 (  0.002075)
  control in object:  0.000000   0.000000   0.000000 (  0.000820)

a.size = 1000 ----------------------------------------------------------
  in a class:         7.200000   0.240000   7.440000 (  7.474116)
  multi-use class:    0.000000   0.000000   0.000000 (  0.003311)
  multi-use object:   0.010000   0.000000   0.010000 (  0.001321)
  control case:       0.000000   0.000000   0.000000 (  0.000402)
  control in class:   0.000000   0.000000   0.000000 (  0.002355)
  control in object:  0.000000   0.000000   0.000000 (  0.000959)
RANK:                                              a = 1000      a = 1
--------------------------------------------------------------------------
6. #in_class                                       1859233%      10721%
4. #in_missing_class                               824%          702%
5. #in_missing_class CONTROL                       585%          4133%
3. #in_missing_object                              329%          315%
2. #in_missing_object CONTROL                      239%          196%
1. #call                                           100%          100%

So again we find that #in_missing_object is still the best in terms of handling various numbers of arguments, and that #in_class (as each is being defined as a method is slowest).

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