CSS4J
DOM mark

DOM benchmarks

Overview

The DOM benchmarks measure how fast different DOM implementations are when building and traversing documents:

The following software versions were used:

The tables and charts were generated by Carte (look for the BenchmarkChartWriter class and the examples folder in the carte-jmh module, especially the dom-benchmark.xml configuration file). The grey lines at the top of the bars give the amplitude of the estimated error.

The computer has an Intel® Core™ i5-1035G7 CPU and 8GB of RAM. Unfortunately, being a laptop processor it is affected by thermal management, which may explain some relatively large error intervals.

(*) Note: one of the tested DOM implementations is the one that comes bundled with the JDK (identified as "JDK" in the graphics), and it has been observed that the version shipped with the Oracle JDK may be faster.

Build HTML documents

Measures the speed at which the validator.nu HTML parser can parse a small document (38 KB HTML) into a few DOM implementations. As a reference, they are compared to the same document parsed by Jsoup.

Code: HTMLBuildBenchmark.

HTML build benchmark HTML build benchmark 38 KB html file, validator.nu parser (except Jsoup) Css4j-DOM4J Css4j DOM DOM4J Css4j DOM (own builder) Jsoup JDK 2590 2072 1554 1036 518 0 Throughput (ops/s) DOM Implementation

Numeric results (higher is better):

ImplementationScoreErrorUnit
Css4j-DOM4J405.62±9.62ops/s
Css4j DOM373.31±1.66ops/s
DOM4J404.08±4.65ops/s
Css4j DOM (own builder)366.70±4.99ops/s
Jsoup2,549.69±8.87ops/s
JDK409.77±29.47ops/s

The standard DOM implementations are somewhat even in this test but Jsoup is much faster, about ten times the JDK and 12 times the speed of the others.

Build small XML documents

The SAX parser that comes bundled with the JDK is used to parse and build a document from a small XHTML file (38 KB).

Code: XMLBuildSmallBenchmark.

XML build benchmark XML build benchmark 38kB file Css4j-DOM4J Css4j DOM DOM4J JDK (css4j builder) Jsoup JDK 1040 832 624 416 208 0 Throughput (ops/s) DOM Implementation

Numeric results (higher is better):

ImplementationScoreErrorUnit
Css4j-DOM4J237.60±2.18ops/s
Css4j DOM217.86±2.57ops/s
DOM4J243.23±13.79ops/s
JDK (css4j builder)241.62±2.43ops/s
Jsoup1,010.23±12.46ops/s
JDK249.69±2.27ops/s

Build XML documents

The SAX parser that comes bundled with the JDK is used to parse and build a document (1MB file).

Code: XMLBuildBenchmark.

XML build benchmark XML build benchmark 1MB file Css4j-DOM4J Css4j DOM DOM4J JDK (css4j builder) Jsoup JDK 57 46 34 23 11 0 Throughput (ops/s) DOM Implementation

Numeric results (higher is better):

ImplementationScoreErrorUnit
Css4j-DOM4J47.282±0.688ops/s
Css4j DOM38.951±0.513ops/s
DOM4J54.042±1.523ops/s
JDK (css4j builder)42.139±8.732ops/s
Jsoup36.428±0.416ops/s
JDK55.206±1.061ops/s

DOM traversal: getFirstChild()/getNextSibling() (small file)

Count the nodes of a 38kB XHTML document, using a combination of getFirstChild()/getNextSibling() to traverse it.

Code: DOMSiblingTraversalSmallMark.

DOM Traversal: NextSibling (small file) DOM Traversal: NextSibling (small file) 38kB file traversed by getFirstChild()/getNextSibling() Css4j DOM Css4j-DOM4J JDK Jsoup 61000 48800 36600 24400 12200 0 Throughput (ops/s) DOM Implementation

Numeric results (higher is better):

ImplementationScoreErrorUnit
Css4j DOM36,340.8±417.2ops/s
Css4j-DOM4J26,651.5±308.0ops/s
JDK59,041.2±699.7ops/s
Jsoup1,324.6±18.0ops/s

Note: for unknown reasons, the usual procedure to build the JDK document with a DocumentBuilderFactory could not be used to initialize the document traversed in the benchmark, as that document is somehow left in an inconsistent state with no child nodes; this happened with the initialization code being executed in a Scope.Benchmark class and also when in a static initialization block. When that same code is executed in a JUnit test, the problem is not seen.

Because of this reason the JDK DOM document was built with css4j's XMLDocumentBuilder, although that process is not part of the timed benchmark.

DOM traversal: getLastChild()/getPreviousSibling() (small file)

Count the nodes of a 38kB XHTML document, using a combination of getLastChild()/getPreviousSibling() to traverse it.

Code: DOMSiblingTraversalSmallMark.

DOM Traversal: PreviousSibling (small file) DOM Traversal: PreviousSibling (small file) 38kB file traversed by getLastChild()/getPreviousSibling() Css4j DOM Css4j-DOM4J JDK Jsoup 61000 48800 36600 24400 12200 0 Throughput (ops/s) DOM Implementation

Numeric results (higher is better):

ImplementationScoreErrorUnit
Css4j DOM36,136.86±415.31ops/s
Css4j-DOM4J26,822.89±294.43ops/s
JDK59,421.46±958.34ops/s
Jsoup1,613.26±9.99ops/s

The Css4j-DOM4J results are representative for DOM4J as well.

DOM traversal: getFirstChild()/getNextSibling()

Count the nodes of an XML document, using a combination of getFirstChild()/getNextSibling() to traverse it.

Code: DOMSiblingTraversalMark.

DOM Traversal: NextSibling DOM Traversal: NextSibling 1MB file traversed by getFirstChild()/getNextSibling() Css4j DOM Css4j-DOM4J JDK Jsoup 3300 2640 1980 1320 660 0 Throughput (ops/s) DOM Implementation

Numeric results (higher is better):

ImplementationScoreErrorUnit
Css4j DOM1,127.71±12.42ops/s
Css4j-DOM4J393.89±7.21ops/s
JDK3,195.08±19.77ops/s
Jsoup1,339.33±12.31ops/s

Note: for unknown reasons, the usual procedure to build the JDK document with a DocumentBuilderFactory could not be used to initialize the document traversed in the benchmark, as that document is somehow left in an inconsistent state with no child nodes; this happened with the initialization code being executed in a Scope.Benchmark class and also when in a static initialization block. When that same code is executed in a JUnit test, the problem is not seen.

Because of this reason the JDK DOM document was built with css4j's XMLDocumentBuilder, although that process is not part of the timed benchmark.

DOM traversal: getLastChild()/getPreviousSibling()

Count the nodes of an XML document, using a combination of getLastChild()/getPreviousSibling() to traverse it.

Code: DOMSiblingTraversalMark.

DOM Traversal: PreviousSibling DOM Traversal: PreviousSibling 1MB file traversed by getLastChild()/getPreviousSibling() Css4j DOM Css4j-DOM4J JDK Jsoup 3500 2800 2100 1400 700 0 Throughput (ops/s) DOM Implementation

Numeric results (higher is better):

ImplementationScoreErrorUnit
Css4j DOM1,035.16±32.05ops/s
Css4j-DOM4J84.53±2.11ops/s
JDK3,402.20±25.92ops/s
Jsoup1,120.81±12.76ops/s

The Css4j-DOM4J results are representative for DOM4J as well.

DOM traversal: NodeIterator (small file)

Count the elements of a 38kB XHTML document traversed by a NodeIterator.

Code: DOMTraversalSmallMark.

DOM Traversal: NodeIterator (small file) DOM Traversal: NodeIterator (small file) 38kB file traversed by NodeIterator Css4j DOM JDK 52000 41600 31200 20800 10400 0 Throughput (ops/s) DOM Implementation

Numeric results (higher is better):

ImplementationScoreErrorUnit
Css4j DOM48,853±1,986ops/s
JDK36,850±1,165ops/s

DOM4J and Jsoup are not included as they lack a NodeIterator.

DOM traversal: NodeIterator

Count the elements of an XML document traversed by a NodeIterator.

Code: DOMTraversalMark.

DOM Traversal: NodeIterator DOM Traversal: NodeIterator 1MB file traversed by NodeIterator Css4j DOM JDK 2110 1688 1266 844 422 0 Throughput (ops/s) DOM Implementation

Numeric results (higher is better):

ImplementationScoreErrorUnit
Css4j DOM851.38±3.68ops/s
JDK2,068.34±14.98ops/s

DOM4J is not included as it lacks a NodeIterator.

Note: sometimes the NodeIterator created by the JDK is in an inconsistent state, and fails with an exception like:

# Warmup Iteration   1: 

java.lang.ArrayIndexOutOfBoundsException: Index 34 out of bounds for length 33
        at java.base/java.util.ArrayList.add(ArrayList.java:455)
        at java.base/java.util.ArrayList.add(ArrayList.java:467)
        at java.xml/com.sun.org.apache.xerces.internal.dom.DocumentImpl.createNodeIterator(DocumentImpl.java:255)
        at io.sf.carte.mark.dom.DOMIteratorMark.markNodeIteratorJdk(DOMIteratorMark.java:46)

But I have observed this only while benchmarking, and not in other cases.

DOM traversal: TreeWalker (small file)

Count the elements of a 38kB XHTML document traversed by a TreeWalker.

Code: DOMTraversalSmallMark.

DOM Traversal: TreeWalker (small file) DOM Traversal: TreeWalker (small file) 38kB file traversed by TreeWalker Css4j DOM JDK 45000 36000 27000 18000 9000 0 Throughput (ops/s) DOM Implementation

Numeric results (higher is better):

ImplementationScoreErrorUnit
Css4j DOM23,469±228ops/s
JDK43,119±506ops/s

Neither DOM4J nor Jsoup provide a TreeWalker.

DOM traversal: TreeWalker

Count the elements of an XML document traversed by a TreeWalker.

Code: DOMTraversalMark.

DOM Traversal: TreeWalker DOM Traversal: TreeWalker 1MB file traversed by TreeWalker Css4j DOM JDK 2370 1896 1422 948 474 0 Throughput (ops/s) DOM Implementation

Numeric results (higher is better):

ImplementationScoreErrorUnit
Css4j DOM853.02±5.17ops/s
JDK2,325.04±17.22ops/s

As mentioned, neither DOM4J nor Jsoup provide a TreeWalker.

DOM traversal: iterator() (small file)

Traverse a 38kB XHTML document using native DOM's iterable getChildNodes(), DOM4J's nodeIterator() and Jsoup's iterable childNodes().

Code: DOMIteratorSmallMark.

DOM Traversal: child node iterators (small file) DOM Traversal: child node iterators (small file) 38kB file traversed by child iterators Css4j DOM DOM4J Jsoup 61000 48800 36600 24400 12200 0 Throughput (ops/s) DOM Implementation

Numeric results (higher is better):

ImplementationScoreErrorUnit
Css4j DOM54,178.4±676.1ops/s
DOM4J59,351.9±796.0ops/s
Jsoup19,750.6±84.1ops/s

The JDK's DOM provides no iterable child collections.

DOM traversal: iterator()

Traverse an XML document using native DOM's iterable getChildNodes() and DOM4J's nodeIterator().

Code: DOMIteratorMark.

DOM Traversal: child node iterators DOM Traversal: child node iterators 1MB file traversed by child iterators Css4j DOM DOM4J Jsoup 19800 15840 11880 7920 3960 0 Throughput (ops/s) DOM Implementation

Numeric results (higher is better):

ImplementationScoreErrorUnit
Css4j DOM964.2±23.2ops/s
DOM4J1,350.8±22.3ops/s
Jsoup19,301.8±231.5ops/s

The JDK's DOM provides no iterable child collections so it was not included.

DOM traversal: elementIterator() (small file)

Traverse a 38kB XHTML document using native DOM's elementIterator(), DOM4J's elementIterator() and Jsoup's iterable Elements.

Code: DOMIteratorSmallMark.

DOM Traversal: element iterators (small file) DOM Traversal: element iterators (small file) 38kB file traversed by elementIterator() Css4j DOM DOM4J Jsoup 54000 43200 32400 21600 10800 0 Throughput (ops/s) DOM Implementation

Numeric results (higher is better):

ImplementationScoreErrorUnit
Css4j DOM49,364.0±162.3ops/s
DOM4J52,038.1±491.8ops/s
Jsoup24,954.8±95.6ops/s

The JDK's DOM provides no element iterator.

DOM traversal: elementIterator()

Traverse an XML document using native DOM's elementIterator() and DOM4J's elementIterator().

Code: DOMIteratorMark.

DOM Traversal: element iterators DOM Traversal: element iterators 1MB file traversed by elementIterator() Css4j DOM DOM4J Jsoup 25700 20560 15420 10280 5140 0 Throughput (ops/s) DOM Implementation

Numeric results (higher is better):

ImplementationScoreErrorUnit
Css4j DOM1,129.65±22.53ops/s
DOM4J996.04±7.54ops/s
Jsoup25,177.72±251.58ops/s

The JDK's DOM provides no element iterator so it was not included.

DOM traversal: getElementsByTagName() (small file)

Traverse the list given by getElementsByTagName() from a 38kB document (it is an XHTML file so Jsoup is included in the comparison). In the case of css4j's native DOM, there are two results: one iterating the NodeList by the item() method, and another via the iterator (the returned NodeList implements Iterable).

Code: DOMElementsByTagNameSmallMark.

DOM getElementsByTagName() (small file) DOM getElementsByTagName() (small file) Traverse 713 elements given by getElementsByTagName() Css4j DOM Css4j-DOM4J Css4j DOM (iterator) JDK Jsoup Jsoup (iterator) 63000 50400 37800 25200 12600 0 Throughput (ops/s) DOM Implementation

Numeric results (higher is better):

ImplementationScoreErrorUnit
Css4j DOM3,341±312ops/s
Css4j-DOM4J56,298±546ops/s
Css4j DOM (iterator)61,182±1,111ops/s
JDK43,595±299ops/s
Jsoup34,126±287ops/s
Jsoup (iterator)34,658±676ops/s

The comparison is not fair as css4j is the only implementation providing live collections as mandated by the DOM specification (live collections change when the document is modified). Despite that, the css4j iterator is the fastest.

DOM traversal: getElementsByTagName()

Traverse the list given by getElementsByTagName(), this time from a 1MB document. Again, for css4j's native DOM there are two results: one iterating the NodeList by the item() method, and another via the iterator (the returned NodeList implements Iterable).

Code: DOMElementsByTagNameMark.

DOM getElementsByTagName() DOM getElementsByTagName() Traverse 3152 elements given by getElementsByTagName() Css4j DOM Css4j-DOM4J Css4j DOM (iterator) JDK Jsoup Jsoup (iterator) 1390 1112 834 556 278 0 Throughput (ops/s) DOM Implementation

Numeric results (higher is better):

ImplementationScoreErrorUnit
Css4j DOM1.0040±0.0205ops/s
Css4j-DOM4J1,313.4453±56.3941ops/s
Css4j DOM (iterator)746.0873±5.2432ops/s
JDK961.7685±20.9898ops/s
Jsoup858.3536±6.3736ops/s
Jsoup (iterator)864.6007±12.8236ops/s

The comparison is not fair as css4j is the only implementation providing live collections as mandated by the DOM specification (live collections change when the document is modified). Despite that, the css4j iterator is still quite fast.

Note: the iterator is documented as the recommended way to traverse the ElementList in css4j since version 3.2. Css4j versions prior to 3.2 perform much better in this benchmark, but the implementation was switched to one that is more lightweight (and the iterator performance is good enough).

DOM modification: appendChild()/removeChild()

Modify the nodes of an XML document by appending elements with appendChild() and later removing them with removeChild().

Code: DOMChangeMark.

DOM document modification DOM document modification appendChild()/removeChild() Css4j DOM Css4j-DOM4J JDK Jsoup 309 247 185 124 62 0 Throughput (ops/s) DOM Implementation

Numeric results (higher is better):

ImplementationScoreErrorUnit
Css4j DOM259.39±4.64ops/s
Css4j-DOM4J66.11±27.03ops/s
JDK217.25±88.00ops/s
Jsoup75.52±7.06ops/s

Analysis

  • Css4j's native DOM has more features than the other contenders (which implies a bit of overhead) but is still quite fast. It is the only implementation whose collections are live (so they change when the document is modified) while the modification itself has no performance penalty.
  • The JDK DOM is fast but is often outperformed by others and lacks element/node iterators.
  • DOM4J used to lag behind in performance, but since the improvements introduced in 2.2.0 it has become much faster and even leads several of the benchmarks. The performance of Css4j-DOM4J is very similar.
  • Jsoup excels at parsing and has good traversal speeds when processing large files. It is not known at this moment why it shows poor performance when doing a sibling traversal on a 38kB XHTML file, but has great performance overall and the document update speeds improved a lot with 1.21.2. The project is actively maintained and, if you do not need the CSS Object Model that is provided by css4j and css4j-dom4j, it is a good choice.