Ruby Dynamic Method Invocation Performance
Ruby is a very flexible and expressive language. A recent question posted by a Ruby newbie got me looking through my IRC logs for a discussion about the performance of various dynamic method invocation approaches, so I thought I’d share some performance results.
Ruby is currently my primary programming language, and I like it a lot; however, the conciseness (except for blocks) and performance of its higher order function capabilities could be improved. Scheme and Haskell beat it in this regard.
Consider three versions of a higher order function, caller, and associated ways of providing information to it to allow invoking a method dynamically given a pre-existing object and argument:
1 2 3 4 5 6 |
# 1. lambda def caller fn fn.call(obj, arg) end caller(lambda {|obj,arg| obj.meth(arg) }) |
1 2 3 4 5 6 |
# 2. send def caller sym obj.send(sym, arg) end caller(:meth) |
1 2 3 4 5 6 |
# 3. bind/call def caller meth meth.bind(obj).call(arg) end caller(MyClass.instance_method(:meth)) |
A simple benchmark of the three approaches with results follows. The benchmark uses an elide(n) method that has been added to String:
1 2 3 4 5 6 7 8 9 |
lam = lambda {|s,n| s.elide(n) } sen = :elide met = String.instance_method(:elide) bm(5) do |x| x.report('lambda') { 100_000.times { lam.call('abcdef',4) } } x.report('send') { 100_000.times { 'abcdef'.send(sen,4) } } x.report('meth') { 100_000.times { met.bind('abc').call(4) } } end |
user system total real
lambda 1.460000 0.510000 1.970000 ( 1.982085)
send 0.810000 0.260000 1.070000 ( 1.075201)
meth 0.660000 0.250000 0.910000 ( 0.913455)
# results with the 3 preparation lines w/in their respective loops
user system total real
lambda 1.900000 0.590000 2.490000 ( 2.498358)
send 0.800000 0.250000 1.050000 ( 1.068035)
meth 0.760000 0.260000 1.020000 ( 1.021275)
I personally like the lambda approach, but Ruby does impose a penalty for using it.