はじめに
Rubyでは、通常のメソッド定義のほかに、動的にメソッドを定義する方法がいくつかありますが、
動的に定義されたメソッドと通常のメソッドの呼出コストの差が気になります。
また、動的に定義されたメソッドの中でもdefine_method
で定義されたものは重いということもよく知られています。
一例として、Railsでは、重いdefine_method
を使う代わりに文字列のclass_eval
を使用している箇所が多くあります。
というわけで、
- 通常のメソッド定義
define_method
-
class_eval
+ 文字列 -
class_eval
+ ブロック
で定義したメソッドの呼出コストのベンチマークを取ってみました。
class_eval
+ ブロックは 正確には動的ではない(メソッドの内容を動的に変更できない)のですが、気になったので同時にベンチマークすることにしました。
ベンチマーク
環境
Ruby 2.1.2
コード
何もしないメソッドをそれぞれの方法で定義し、10000000回呼び出しています。
require 'benchmark'
class Test
# 通常のメソッド
def test_normal
end
# define_method
define_method :test_define_method do
end
# class_eval (文字列)
class_eval <<-EOS, __FILE__, __LINE__ + 1
def test_eval_string
end
EOS
# class_eval (ブロック)
class_eval do
def test_eval_block
end
end
end
n = 10000000
test = Test.new
Benchmark.bm do |b|
b.report '通常のメソッド' do
n.times { test.test_normal }
end
b.report 'define_method' do
n.times { test.test_define_method }
end
b.report 'class_eval (文字列)' do
n.times { test.test_eval_string }
end
b.report 'class_eval (ブロック)' do
n.times { test.test_eval_block }
end
end
結果
define_method
版が他に比べて2倍近く時間がかかる一方、他の方法はほとんど時間が変わらないことがわかりました。
user system total real
通常のメソッド 0.880000 0.000000 0.880000 ( 0.880378)
define_method 1.520000 0.000000 1.520000 ( 1.527402)
class_eval (文字列) 0.890000 0.000000 0.890000 ( 0.893508)
class_eval (ブロック) 0.880000 0.000000 0.880000 ( 0.884782)
まとめ
やはりdefine_method
で定義したメソッドは遅い
define_method
版は他に比べて2倍近く遅いという結果になりました。
今回は何も行わないメソッドでのベンチマークだったので結果が極端になりましたが、
通常のプログラミングでも、メソッド呼出のコストを考慮しなければいけない場合 (メソッドの内容が少ないなど) は、define_method
の遅さを気にする必要がありそうです。
文字列class_eval
で定義したメソッドは通常のメソッドと呼出コストが変わらない
一方、文字列class_eval
を使えば、通常のメソッドと同じ速さのメソッドを動的に定義できる (文字列しか動的に操作できないというデメリットがありますが) ということもわかりました。