クラスの探索

文字列からクラスに変換するのに今までは、ObjectSpace.each_objectを使っていたけれど、これが結構遅い。

# 旧コード:遅いけれど、柔軟
def find_by_each(name)
  ObjectSpace.each_object(Class) do|klass|
    if klass.to_s.downcase == name.downcase
      return klass
    end
  end
end

そこで、文字列をevalするようにしてみた。ただし単純にevalするだけだと不便なので、foo::bar::bazをFoo::Bar::Bazのように変換するようにしてみた。

# 新コード:速いけれど、柔軟性はあまりない
def find_by_eval(name)
  eval name.split('::').map{|x| x.capitalize }.join('::')
end

そして、次のようなコードを用意して速度を比較してみた。

100.times{
  find_by_each 'string'
}

100.times{
  find_by_eval 'string'
}
$ ruby -r profile find_obj.rb 2>&1 | grep 'find_by_'
  0.28     6.88      0.02      100     0.20     0.50  Object#find_by_eval
  0.00     7.04      0.00      100     0.00    62.90  Object#find_by_each

ちなみに左から、

  1. 全体時間のパーセンテージ
  2. 全体時間の総和(単位は秒)
  3. 正味時間の総和(秒)
  4. 呼び出された回数
  5. 1回の呼び出し当たりの平均正味時間(ミリ秒)
  6. 1回の呼び出し当たりの平均全体時間(ミリ秒)
  7. メソッド名

となっている。

つまり、evalを使ったほうが100倍以上速くなっている。ただし、LongNameFileのように途中に大文字が現れるクラスには対応できなくなった。