From 96193258c2b866ddedc6bdaa7af36f82b0dc89ee Mon Sep 17 00:00:00 2001 From: NAITOH Jun Date: Tue, 5 May 2026 20:17:12 +0900 Subject: [PATCH] Optimization of REXML::Document#add, #add_element ## Benchmark ``` $ benchmark-driver benchmark/parse_comment.yaml before after before(YJIT) after(YJIT) top_level 48.497k 54.645k 7.951k 8.288k i/s - 100.000 times in 0.002062s 0.001830s 0.012577s 0.012066s in_doctype 48.615k 50.994k 7.930k 7.820k i/s - 100.000 times in 0.002057s 0.001961s 0.012611s 0.012787s after_doctype 63.251k 64.061k 9.569k 9.434k i/s - 100.000 times in 0.001581s 0.001561s 0.010450s 0.010600s many_comments 67.653 611.015 117.191 929.290 i/s - 100.000 times in 1.478125s 0.163662s 0.853309s 0.107609s Comparison: top_level after: 54644.8 i/s before: 48496.6 i/s - 1.13x slower after(YJIT): 8287.7 i/s - 6.59x slower before(YJIT): 7951.0 i/s - 6.87x slower in_doctype after: 50994.4 i/s before: 48614.5 i/s - 1.05x slower before(YJIT): 7929.6 i/s - 6.43x slower after(YJIT): 7820.4 i/s - 6.52x slower after_doctype after: 64061.5 i/s before: 63251.1 i/s - 1.01x slower before(YJIT): 9569.4 i/s - 6.69x slower after(YJIT): 9434.0 i/s - 6.79x slower many_comments after(YJIT): 929.3 i/s after: 611.0 i/s - 1.52x slower before(YJIT): 117.2 i/s - 7.93x slower before: 67.7 i/s - 13.74x slower ``` - YJIT=ON : 0.98 - 7.93x faster - YJIT=OFF : 1.01 - 9.02x faster --- benchmark/parse_comment.yaml | 4 +++- lib/rexml/document.rb | 10 ++++---- test/test_document.rb | 44 ++++++++++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 7 deletions(-) diff --git a/benchmark/parse_comment.yaml b/benchmark/parse_comment.yaml index b9d7fdc0..1d6942fa 100644 --- a/benchmark/parse_comment.yaml +++ b/benchmark/parse_comment.yaml @@ -24,13 +24,15 @@ contexts: prelude: | require 'rexml/document' - SIZE = 100000 + SIZE = 1000 top_level_xml = "\n" in_doctype_xml = "]>" after_doctype_xml = "" + many_comments_xml = "" * SIZE + "" benchmark: 'top_level' : REXML::Document.new(top_level_xml) 'in_doctype' : REXML::Document.new(in_doctype_xml) 'after_doctype' : REXML::Document.new(after_doctype_xml) + 'many_comments' : REXML::Document.new(many_comments_xml) diff --git a/lib/rexml/document.rb b/lib/rexml/document.rb index 3d61dc07..d5db713d 100644 --- a/lib/rexml/document.rb +++ b/lib/rexml/document.rb @@ -197,9 +197,8 @@ def add( child ) end child.parent = self else - rv = super - raise "attempted adding second root element to document" if @elements.size > 1 - rv + raise "attempted adding second root element to document" if child.kind_of?(Element) && root + super end end alias :<< :add @@ -211,9 +210,8 @@ def add( child ) # # REXML::Element.add_element(name_or_element, attributes) def add_element(arg=nil, arg2=nil) - rv = super - raise "attempted adding second root element to document" if @elements.size > 1 - rv + raise "attempted adding second root element to document" if root + super end # :call-seq: diff --git a/test/test_document.rb b/test/test_document.rb index 71fed190..16c3da7c 100644 --- a/test/test_document.rb +++ b/test/test_document.rb @@ -320,6 +320,50 @@ def test_encoding end end + class AddTest < Test::Unit::TestCase + def test_add_second_root_element_raises + doc = REXML::Document.new("") + assert_raise(RuntimeError, "attempted adding second root element to document") do + doc.add(REXML::Element.new("second")) + end + end + + def test_append_operator_second_root_element_raises + doc = REXML::Document.new("") + assert_raise(RuntimeError, "attempted adding second root element to document") do + doc << REXML::Element.new("second") + end + end + + def test_add_element_second_root_raises + doc = REXML::Document.new("") + assert_raise(RuntimeError, "attempted adding second root element to document") do + doc.add_element("second") + end + end + + def test_add_element_with_element_second_root_raises + doc = REXML::Document.new("") + assert_raise(RuntimeError, "attempted adding second root element to document") do + doc.add_element(REXML::Element.new("second")) + end + end + + def test_add_xml_decl_allowed + doc = REXML::Document.new("") + assert_nothing_raised do + doc.add(REXML::XMLDecl.new("1.0")) + end + end + + def test_add_doctype_allowed + doc = REXML::Document.new("") + assert_nothing_raised do + doc.add(REXML::DocType.new("root")) + end + end + end + class BomTest < Test::Unit::TestCase class HaveEncodingTest < self def test_utf_8