Here is all possible method calls:
require 'benchmark/ips'
class FooBar
def name; end
end
el = FooBar.new
Benchmark.ips do |x|
x.report('plain') { el.name }
x.report('eval') { eval('el.name') }
x.report('method call') { el.method(:name).call }
x.report('send sym') { el.send(:name) }
x.report('send str') { el.send('name') }
x.compare!
end
And results are:
Warming up --------------------------------------
plain 236.448k i/100ms
eval 20.743k i/100ms
method call 131.408k i/100ms
send sym 205.491k i/100ms
send str 168.137k i/100ms
Calculating -------------------------------------
plain 9.150M (± 6.5%) i/s - 45.634M in 5.009566s
eval 232.303k (± 5.4%) i/s - 1.162M in 5.015430s
method call 2.602M (± 4.5%) i/s - 13.009M in 5.010535s
send sym 6.729M (± 8.6%) i/s - 33.495M in 5.016481s
send str 4.027M (± 5.7%) i/s - 20.176M in 5.027409s
Comparison:
plain: 9149514.0 i/s
send sym: 6729490.1 i/s - 1.36x slower
send str: 4026672.4 i/s - 2.27x slower
method call: 2601777.5 i/s - 3.52x slower
eval: 232302.6 i/s - 39.39x slower
It's expected that plain call is the fastest, no any additional allocations, symbol lookups, just lookup and evaluation of method.
As for send
via symbol, it is faster than via string as its much more easer to allocate memory for symbol. Once it's defined it's stored for long term in memory and there no reallocations.
The same reason can be said about method(:name)
(1) it's requires to allocate memory for Proc
object (2) we are calling the method in class which leads for additional method lookup which takes time too.
eval
is runs interpreter so it's the heaviest.