nokogiri などの XML パーサー ライブラリを使用して XML テキストを読み取り、ノードのツリーを生成する XML データ スキャナーを作成しています。XML 要素ごとにオブジェクトを作成する必要があります。したがって、使用しているパーサー ライブラリ オプション (SAX または DOM) の種類に関係なく、次のように、指定された要素名と属性に従ってオブジェクトを作成するメソッドが必要です。
create_node(name, attributes_hash)
このメソッドは、に従って分岐する必要がありname
ます。実装の可能性は次のとおりです。
- ケースステートメント
- メソッドディスパッチと定義済みメソッド
この方法がボトルネックになる可能性があるため、Ruby の動作を確認するベンチマーク スクリプトを作成しました。(この質問の最後の部分に添付されたベンチマークスクリプト。スクリプトの一部、特にケースステートメントの作成方法が気に入らないので、これ を改善する方法についてのコメントも歓迎しますが、次のように提供してくださいコメントは答えではありません...おそらくそのための質問も作成する必要があります..)。
このスクリプトは、次の 4 つのケースを 2 つの範囲サイズで測定します。
- 定数名によるメソッドディスパッチ
- 名前を連結したメソッドディスパッチ
#{}
- 名前を連結したメソッドディスパッチ
+
- 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 を作成していくつかのメソッドを呼び出すことができますが、これは望ましくありません。しかし、メソッド名を決定するためのコストは無視できません。
これらのスキャナーをどのように記述しますか? 次の質問に対する答えを知りたいです。
- 上記以外に良いスキームはありますか?
- そうでない場合、どの (case-when または method dispatch) スキームを選択しますか?
- メソッド名を計算しないと、より高速になります。メソッドディスパッチを安全に行う良い方法はありますか? (たとえば、呼び出されるノード名を制限することによって。)
ベンチマーク スクリプト
# 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)