DOM benchmarks
Overview
The DOM benchmarks measure how fast different DOM implementations are when building and traversing documents:
- Css4j's native DOM.
- Css4j-DOM4J module (which subclasses DOM4J).
- Stand-alone DOM4J.
- JDK's bundled DOM implementation.
- Jsoup does not implement DOM but is a popular HTML parser and pseudo-DOM library.
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.
Numeric results (higher is better):
| Implementation | Score | Error | Unit |
|---|---|---|---|
| Css4j-DOM4J | 405.62 | ±9.62 | ops/s |
| Css4j DOM | 373.31 | ±1.66 | ops/s |
| DOM4J | 404.08 | ±4.65 | ops/s |
| Css4j DOM (own builder) | 366.70 | ±4.99 | ops/s |
| Jsoup | 2,549.69 | ±8.87 | ops/s |
| JDK | 409.77 | ±29.47 | ops/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.
Numeric results (higher is better):
| Implementation | Score | Error | Unit |
|---|---|---|---|
| Css4j-DOM4J | 237.60 | ±2.18 | ops/s |
| Css4j DOM | 217.86 | ±2.57 | ops/s |
| DOM4J | 243.23 | ±13.79 | ops/s |
| JDK (css4j builder) | 241.62 | ±2.43 | ops/s |
| Jsoup | 1,010.23 | ±12.46 | ops/s |
| JDK | 249.69 | ±2.27 | ops/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.
Numeric results (higher is better):
| Implementation | Score | Error | Unit |
|---|---|---|---|
| Css4j-DOM4J | 47.282 | ±0.688 | ops/s |
| Css4j DOM | 38.951 | ±0.513 | ops/s |
| DOM4J | 54.042 | ±1.523 | ops/s |
| JDK (css4j builder) | 42.139 | ±8.732 | ops/s |
| Jsoup | 36.428 | ±0.416 | ops/s |
| JDK | 55.206 | ±1.061 | ops/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.
Numeric results (higher is better):
| Implementation | Score | Error | Unit |
|---|---|---|---|
| Css4j DOM | 36,340.8 | ±417.2 | ops/s |
| Css4j-DOM4J | 26,651.5 | ±308.0 | ops/s |
| JDK | 59,041.2 | ±699.7 | ops/s |
| Jsoup | 1,324.6 | ±18.0 | ops/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.
Numeric results (higher is better):
| Implementation | Score | Error | Unit |
|---|---|---|---|
| Css4j DOM | 36,136.86 | ±415.31 | ops/s |
| Css4j-DOM4J | 26,822.89 | ±294.43 | ops/s |
| JDK | 59,421.46 | ±958.34 | ops/s |
| Jsoup | 1,613.26 | ±9.99 | ops/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.
Numeric results (higher is better):
| Implementation | Score | Error | Unit |
|---|---|---|---|
| Css4j DOM | 1,127.71 | ±12.42 | ops/s |
| Css4j-DOM4J | 393.89 | ±7.21 | ops/s |
| JDK | 3,195.08 | ±19.77 | ops/s |
| Jsoup | 1,339.33 | ±12.31 | ops/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.
Numeric results (higher is better):
| Implementation | Score | Error | Unit |
|---|---|---|---|
| Css4j DOM | 1,035.16 | ±32.05 | ops/s |
| Css4j-DOM4J | 84.53 | ±2.11 | ops/s |
| JDK | 3,402.20 | ±25.92 | ops/s |
| Jsoup | 1,120.81 | ±12.76 | ops/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.
Numeric results (higher is better):
| Implementation | Score | Error | Unit |
|---|---|---|---|
| Css4j DOM | 48,853 | ±1,986 | ops/s |
| JDK | 36,850 | ±1,165 | ops/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.
Numeric results (higher is better):
| Implementation | Score | Error | Unit |
|---|---|---|---|
| Css4j DOM | 851.38 | ±3.68 | ops/s |
| JDK | 2,068.34 | ±14.98 | ops/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.
Numeric results (higher is better):
| Implementation | Score | Error | Unit |
|---|---|---|---|
| Css4j DOM | 23,469 | ±228 | ops/s |
| JDK | 43,119 | ±506 | ops/s |
Neither DOM4J nor Jsoup provide a TreeWalker.
DOM traversal: TreeWalker
Count the elements of an XML document traversed by a TreeWalker.
Code: DOMTraversalMark.
Numeric results (higher is better):
| Implementation | Score | Error | Unit |
|---|---|---|---|
| Css4j DOM | 853.02 | ±5.17 | ops/s |
| JDK | 2,325.04 | ±17.22 | ops/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.
Numeric results (higher is better):
| Implementation | Score | Error | Unit |
|---|---|---|---|
| Css4j DOM | 54,178.4 | ±676.1 | ops/s |
| DOM4J | 59,351.9 | ±796.0 | ops/s |
| Jsoup | 19,750.6 | ±84.1 | ops/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.
Numeric results (higher is better):
| Implementation | Score | Error | Unit |
|---|---|---|---|
| Css4j DOM | 964.2 | ±23.2 | ops/s |
| DOM4J | 1,350.8 | ±22.3 | ops/s |
| Jsoup | 19,301.8 | ±231.5 | ops/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.
Numeric results (higher is better):
| Implementation | Score | Error | Unit |
|---|---|---|---|
| Css4j DOM | 49,364.0 | ±162.3 | ops/s |
| DOM4J | 52,038.1 | ±491.8 | ops/s |
| Jsoup | 24,954.8 | ±95.6 | ops/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.
Numeric results (higher is better):
| Implementation | Score | Error | Unit |
|---|---|---|---|
| Css4j DOM | 1,129.65 | ±22.53 | ops/s |
| DOM4J | 996.04 | ±7.54 | ops/s |
| Jsoup | 25,177.72 | ±251.58 | ops/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.
Numeric results (higher is better):
| Implementation | Score | Error | Unit |
|---|---|---|---|
| Css4j DOM | 3,341 | ±312 | ops/s |
| Css4j-DOM4J | 56,298 | ±546 | ops/s |
| Css4j DOM (iterator) | 61,182 | ±1,111 | ops/s |
| JDK | 43,595 | ±299 | ops/s |
| Jsoup | 34,126 | ±287 | ops/s |
| Jsoup (iterator) | 34,658 | ±676 | ops/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.
Numeric results (higher is better):
| Implementation | Score | Error | Unit |
|---|---|---|---|
| Css4j DOM | 1.0040 | ±0.0205 | ops/s |
| Css4j-DOM4J | 1,313.4453 | ±56.3941 | ops/s |
| Css4j DOM (iterator) | 746.0873 | ±5.2432 | ops/s |
| JDK | 961.7685 | ±20.9898 | ops/s |
| Jsoup | 858.3536 | ±6.3736 | ops/s |
| Jsoup (iterator) | 864.6007 | ±12.8236 | ops/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.
Numeric results (higher is better):
| Implementation | Score | Error | Unit |
|---|---|---|---|
| Css4j DOM | 259.39 | ±4.64 | ops/s |
| Css4j-DOM4J | 66.11 | ±27.03 | ops/s |
| JDK | 217.25 | ±88.00 | ops/s |
| Jsoup | 75.52 | ±7.06 | ops/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.