Ruby - Metaprogramming & Performance

When is it best to use metaprogramming?

Yesterday I shared two examples of using define_method. Curiosity drove me to take it a step further with benchmarks to compare performance between both approaches.

Approach 1:

require 'benchmark'

class Brain
  define_method("process_thinking") do |argument|
    "Thinking about #{argument}."
  end

  define_method("process_dreaming") do |argument|
    "Dreaming about #{argument}."
  end

  define_method("process_feeling_good") do |argument|
    "Feeling good about #{argument}."
  end
end

n = 1000000
Benchmark.bm do |benchmark|
  benchmark.report do
    n.times do
      mind = Brain.new
      mind.process_thinking("friends")  # => Thinking about friends.
      mind.process_dreaming("eagles")  # => Dreaming about eagles.
      mind.process_feeling_good("eating chicken")  # => Feeling good about eating chicken.
    end
  end
end

Approach 2 (metaprogramming):

require 'benchmark'

class Brain
  ["Thinking", "Dreaming", "Feeling_good"].each do |subject|
    define_method("process_#{subject.downcase}") do |argument|
      "#{subject.gsub('_', ' ')} about #{argument}."
    end
  end
end

n = 1000000
Benchmark.bm do |benchmark|
  benchmark.report do
    n.times do
      mind = Brain.new
      mind.process_thinking("friends")  # => Thinking about friends.
      mind.process_dreaming("eagles")  # => Dreaming about eagles.
      mind.process_feeling_good("eating chicken")  # => Feeling good about eating chicken.
    end
  end
end

Approach 1 results:

user      system    total     real
1.250000  0.000000  1.250000  (1.245180)

Approach 2 results:

user      system    total     real
3.490000  0.000000  3.490000  (3.486967)

I’m still growing in my understanding of metaprogramming and instances where it might be most useful, but you’ll notice that the example in the second approach is powerful in that it accomplishes the same result with less lines of code. While that may seem like a benefit in developer-efficiency, it carries a significant performance cost over many cycles.

These examples are trivial in what they do, but the comparison reiterates the idea of carefully evaluating when it’s better to use one approach over the other.