3

nokogiri などの XML パーサー ライブラリを使用して XML テキストを読み取り、ノードのツリーを生成する XML データ スキャナーを作成しています。XML 要素ごとにオブジェクトを作成する必要があります。したがって、使用しているパーサー ライブラリ オプション (SAX または DOM) の種類に関係なく、次のように、指定された要素名と属性に従ってオブジェクトを作成するメソッドが必要です。

create_node(name, attributes_hash)

このメソッドは、に従って分岐する必要がありnameます。実装の可能性は次のとおりです。

  1. ケースステートメント
  2. メソッドディスパッチと定義済みメソッド

この方法がボトルネックになる可能性があるため、Ruby の動作を確認するベンチマーク スクリプトを作成しました。(この質問の最後の部分に添付されたベンチマークスクリプト。スクリプトの一部、特にケースステートメントの作成方法が気に入らないので、これ を改善する方法についてのコメントも歓迎しますが、次のように提供してくださいコメントは答えではありません...おそらくそのための質問も作成する必要があります..)。

このスクリプトは、次の 4 つのケースを 2 つの範囲サイズで測定します。

  1. 定数名によるメソッドディスパッチ
  2. 名前を連結したメソッドディスパッチ#{}
  3. 名前を連結したメソッドディスパッチ+
  4. caseステートメントを使用して、同じメソッドを呼び出します

結果:

                                                 user     system      total        real
a to z: method_calls (with const name)       0.090000   0.000000   0.090000 (  0.092516)
a to z: method_calls (with dynamic name) 1   0.180000   0.000000   0.180000 (  0.181793)
a to z: method_calls (with dynamic name) 2   0.200000   0.000000   0.200000 (  0.202818)
a to z: switch_calls                         0.130000   0.000000   0.130000 (  0.132633)

                                                user     system      total        real
a to zz: method_calls (with const name)       2.900000   0.000000   2.900000 (  2.894273)
a to zz: method_calls (with dynamic name) 1   6.500000   0.010000   6.510000 (  6.507099)
a to zz: method_calls (with dynamic name) 2   6.980000   0.000000   6.980000 (  6.987534)
a to zz: switch_calls                         4.750000   0.000000   4.750000 (  4.742448)

const 名ベースのメソッド ディスパッチは、case ステートメントを使用するよりも高速であることがわかりますが、メソッド名を決定するときに文字列操作が含まれる場合、メソッド名を決定するためのコストは、実際のメソッド呼び出しのコストよりも高くなり、効果的にこれらのオプションを作成します (2 および 3)。 ) オプション 4 よりも遅くなります。また、オプション 2 と 3 の違いはごくわずかです。

スキャナーを安全にするために、私はメソッドにプレフィックスを付けたいと思っています。これがないと、XML を作成していくつかのメソッドを呼び出すことができますが、これは望ましくありません。しかし、メソッド名を決定するためのコストは無視できません。

これらのスキャナーをどのように記述しますか? 次の質問に対する答えを知りたいです。

  1. 上記以外に良いスキームはありますか?
  2. そうでない場合、どの (case-when または method dispatch) スキームを選択しますか?
  3. メソッド名を計算しないと、より高速になります。メソッドディスパッチを安全に行う良い方法はありますか? (たとえば、呼び出されるノード名を制限することによって。)

ベンチマーク スクリプト

# Benchmark to measure the difference of
# use of case statement and message passing

require 'benchmark'

def bench(title, tobj, count)
  Benchmark.bmbm do |b|
    b.report "#{title}: method_calls (with const name)" do
      (1..count).each do |c|
        tobj.run_send_using_const
      end
    end

    b.report "#{title}: method_calls (with dynamic name) 1" do
      (1..count).each do |c|
        tobj.run_send_using_dynamic_1
      end
    end

    b.report "#{title}: method_calls (with dynamic name) 2" do
      (1..count).each do |c|
        tobj.run_send_using_dynamic_2
      end
    end

    b.report "#{title}: switch_calls" do
      (1..count).each do |c|
        tobj.run_switch
      end
    end
  end
end


class Switcher
  def initialize(names)
    @method_names = { }
    @names = names
    names.each do |n|
      @method_names[n] = "dynamic_#{n}"
      @@n = n
      class << self
        mname = "dynamic_#{@@n}"
        define_method(mname) do
          mname
        end
      end
    end

    swst = ""
    names.each do |n|
      swst << "when \"#{n}\" then dynamic_#{n}\n"
    end

    st = "
    def run_switch_each(n)
      case n
#{swst}
      end
    end
    "
    eval(st)
  end

  def run_send_using_const
    @method_names.each_value do |n|
      self.send n
    end
  end

  def run_send_using_dynamic_1
    @names.each do |n|
      self.send "dynamic_#{n}"
    end
  end

  def run_send_using_dynamic_2
    @names.each do |n|
      self.send "dynamic_" + n
    end
  end

  def run_switch
    @names.each do |n|
      run_switch_each(n)
    end
  end

end


sw1 = Switcher.new('a'..'z')
sw2 = Switcher.new('a'..'zz')

bench("a to z", sw1, 10000)
bench("a to zz", sw2, 10000)
4

1 に答える 1

2

これは時期尚早の最適化のケースだと思います。

しかし、メソッド名を決定するためのコストは無視できません。

何と比べて無視できない?ここでのアプローチのパフォーマンス数値は異なりますが、1 つのノードをディスパッチするのにかかる時間は、ノードを解析して (ノコギリなどを使用して)、特殊なノード オブジェクトを構築し、それを使用して必要なことを行うのにかかる時間に匹敵しますか?

私はそれがないと信じています。そのステートメントを証明するためのベンチマークはありませんが (実際のコードが必要です)、文字列の連結と文字列の補間が実際に結果 (dynamic1 と dynamic2) に顕著な違いをもたらすという事実は、あなたが些細なことを測る。

または、ディスパッチごとに 1 つの文字列連結を追加すると、結果として時間が 2 ~ 2.5 倍になります (const と dynamic2)。

于 2012-07-10T13:30:51.973 に答える