From 3369f96b1316f675a4d25e709a701233dbba7781 Mon Sep 17 00:00:00 2001 From: NAITOH Jun Date: Mon, 15 Sep 2025 14:47:11 +0900 Subject: [PATCH] Optimization of REXML::Parsers::SAX2Parser#get_namespace ## Benchmark ``` before after before(YJIT) after(YJIT) sax 1.415k 2.297k 1.755k 2.958k i/s - 100.000 times in 0.070687s 0.043539s 0.056966s 0.033810s Comparison: sax after(YJIT): 2957.7 i/s after: 2296.8 i/s - 1.29x slower before(YJIT): 1755.4 i/s - 1.68x slower before: 1414.7 i/s - 2.09x slower ``` - YJIT=ON : 1.68x faster - YJIT=OFF : 1.62x faster --- benchmark/sax.yaml | 34 +++++++++++++++++++++++++++++++++ lib/rexml/parsers/sax2parser.rb | 11 ++++------- 2 files changed, 38 insertions(+), 7 deletions(-) create mode 100644 benchmark/sax.yaml diff --git a/benchmark/sax.yaml b/benchmark/sax.yaml new file mode 100644 index 00000000..1cc27d7e --- /dev/null +++ b/benchmark/sax.yaml @@ -0,0 +1,34 @@ +loop_count: 100 +contexts: + - gems: + rexml: 3.2.6 + require: false + prelude: require 'rexml' + - name: master + prelude: | + $LOAD_PATH.unshift(File.expand_path("lib")) + require 'rexml' + - name: 3.2.6(YJIT) + gems: + rexml: 3.2.6 + require: false + prelude: | + require 'rexml' + RubyVM::YJIT.enable + - name: master(YJIT) + prelude: | + $LOAD_PATH.unshift(File.expand_path("lib")) + require 'rexml' + RubyVM::YJIT.enable + +prelude: | + require 'rexml/parsers/sax2parser' + + DEPTH = 100 + xml = '' * DEPTH + '' * DEPTH + +benchmark: + 'sax' : | + parser = REXML::Parsers::SAX2Parser.new(xml) + parser.listen(:start_element){|uri, localname, qname, attrs|} + parser.parse diff --git a/lib/rexml/parsers/sax2parser.rb b/lib/rexml/parsers/sax2parser.rb index a51477de..79838528 100644 --- a/lib/rexml/parsers/sax2parser.rb +++ b/lib/rexml/parsers/sax2parser.rb @@ -12,7 +12,7 @@ def initialize source @parser = BaseParser.new(source) @listeners = [] @procs = [] - @namespace_stack = [] + @namespace_stack = [{}] @has_listeners = false @tag_stack = [] @entities = {} @@ -122,7 +122,7 @@ def parse event[2].each {|n, v| event[2][n] = @parser.normalize(v)} nsdecl = event[2].find_all { |n, value| n =~ /^xmlns(:|$)/ } nsdecl.collect! { |n, value| [ n[6..-1], value ] } - @namespace_stack.push({}) + @namespace_stack.push(@namespace_stack[-1].dup) nsdecl.each do |n,v| @namespace_stack[-1][n] = v # notify observers of namespaces @@ -259,11 +259,8 @@ def add( pair ) end def get_namespace( prefix ) - return nil if @namespace_stack.empty? - - uris = (@namespace_stack.find_all { |ns| not ns[prefix].nil? }) || - (@namespace_stack.find { |ns| not ns[nil].nil? }) - uris[-1][prefix] unless uris.nil? or 0 == uris.size + current_namespace = @namespace_stack[-1] + current_namespace[prefix] || current_namespace[nil] end end end