Benchmarks

Introduction

Benchmark results are always dependent on a very individual setup. Normally it is not useful to generalize such results for every use case, but it can give you a hint. However, if you're really in the need of maximum performance, you should probably create an own benchmark with your objects or even use a profiler to detect the real hot spots in your application.

XStream uses the Java Microbenchmark Harness (JMH) of the JDK Tools as benchmark framework starting with version 1.4.9. As result it contains a ZIP file (xstream-jmh-<version>-app.zip) as new artifact containing anything required to run the benchmarks. Unpack the file and use the scripts in the bin directory to execute the benchmarks. Use option -h to look at the options provided by JMH. You may exchange the libraries in the lib directory with other versions of XStream or the individual parsers or you may even add new JMH benchmarks to the default ones of XStream.

All benchmark values below measure the average throughput in nanosecond per operation. JMH provides additional measurement options, see online help. The maximum deviation for each benchmark is recorded in the reference files of the distributed ZIP file. The benchmark is executed on Linux 5.15.11 Gentoo 64-bit system with an Intel Core i7 CPU 920 of 2.67 GHz using OpenJDK 11.0.13. Note again, that these values are no replacement for real profiler results and they may vary from run to run (see reference files) due to this machine's background processes. However, it can give you some idea of what you can expect using different parser technologies.

Parser Benchmark

The values represent the average throughput of 15 runs with a single thread. The benchmarks emphasis the parser efficiency for different structures.

Parser Text Array Nested
W3C DOM (Open JDK 11.0.13) 10553104.053 58632015.971 5321471.291
JDOM (1.1.3) 6347929.561 7102275.757 16861677.394
JDOM 2 (2.0.5) 5843003.401 9827411.961 12085612.224
DOM4J (1.6.1) 8344385.552 78757514.580 5711026.345
XOM (1.1) 7986743.807 33930673.083 5788240.908
StAX (BEA 1.2.0) 3229409.245 713536.588 648266.777
StAX (Woodstox 3.2.7) 2048393.986 592419.675 725660.904
StAX (Open JDK 11.0.13) 8377577.926 700802.493 1074253.465
XPP (MXParser 1.2.2) 2090782.658 687905.727 12616894.304
XPP (Xpp3 min 1.1.4c) 2112720.726 701583.341 13007586.291
XPP (kXML2 min 2.3.0) 3524809.724 902275.516 35970087.264
Binary (XStream 1.4.16) 1111084.176 402398.155 315810.980
Jettison (1.2) 3617569.912 670870.406 735876.170
Text
A single element with a text of 100.000 characters.
Array
A single element with 1.000 child elements.
Nested
Nested elements in 1000 levels (since version 1.4.10).

Converter Type Benchmark

The values represent the average throughput of 16 runs with four threads using the Xpp3 parser for a structure with 1.000 elements. The benchmarks demonstrate the different converter types that can be used for a standard Java class.

Converter Type Throughput
Custom 9666231.183
Java Bean 18907234.350
Reflection 20777749.230
Custom
A converter especially written for the Java type to convert.
Java Bean
Usage of the generic JavaBeanConverter, since the Java type respects the Java Bean contract.
Reflection
Usage of the generic converter based on reflection.

String Converter Benchmark

The values represent the average throughput of 16 runs with four threads using the Xpp3 parser for a structure with 10.000 string elements of various sizes and duplicates. The benchmarks demonstrate different implementations and configurations of the StringConverter.

StringConverter Strategy Throughput
No Cache 11982049.168
Intern 15280597.717
ConcurrentMap (length limit) 10812523.401
ConcurrentMap (unlimited) 12196204.773
Sync'd WeakCache (length limit) 11476639.041
Sync'd WeakCache (unlimited) 11346761.846
No Cache
An implementation that does not cache deserialized String values with the consequence that repeated values will always allocate separate memory.
Intern
An implementation that uses String.intern() to cache the individual values. The memory pool used for the values is dependent on the JDK version. Up to Java 7 this was the permanent generation space i.e. the memory has to be shared with all loaded classes. It is up to the garbage collection when these string values are freed again.
ConcurrentMap (length limit)
An implementation that uses a ConcurrentHashMap as cache for strings of limited length (38 characters). The lifetime of the cache is equivalent with the lifetime of the XStream instance.
ConcurrentMap (unlimited)
An implementation that uses a ConcurrentHashMap as cache for all strings. The lifetime of the cache is equivalent with the lifetime of the XStream instance.
Sync'd WeakCache (length limit)
An implementation that uses a WeakCache for strings of limited length (38 characters). This cache uses weak references for its keys and values. An entry is therefore only kept as long as the deserialized object structure is referencing it. This is XStream's default strategy.
Syn'd WeakCache (unlimited)
An implementation that uses a WeakCache for all strings. This cache uses weak references for its keys and values. An entry is therefore ony kept as long as the deserialized object structure is referencing it.

Name Coder Benchmark

The values represent the average throughput of 25 runs with four threads using the Xpp3 parser for a structure with 250 nested elements using names invalid for XML elements. The benchmarks demonstrate different implementation strategies for a NameCoder to create valid tag names in XML.

NameCoder Implementation Throughput
No Coding 4212316.966
Dollar Coding 4843325.489
Escaped Underscore Coding 6496347.261
Cached Escaped Underscore Coding 4708590.172
XML Friendly Coding 5122809.546
No Coding
An implementation that does not encode the names of XML elements. It relies on the fact that the object graph does not contain elements with invalid XML names, because the name of the class types and members are either conforming or have been aliased.
Dollar Coding
An implementation that uses String.replace to replace any dollar sign with '·' (middle dot), a valid character normally not used for Java identifiers. In typical Java code there are no other invalid characters used for Java identifiers, however, Java allows identifiers to contain a wide range of UTF-8 characters and the JVM has even less restrictions.
Escaped Underscore Coding
An implementation that uses a StringBuilder to create the XML name by replacing any dollar sign with '_-' and escapes every plain underscore with two ones. The implementation will therefore only use characters in the standard ASCII range. It is not possible to use a simple minus sign as replacement because it is not a valid first character for XML names. However, the comments about Java identifiers and JVM identifiers from the Dollar Coding still apply.
Cached Escaped Underscore Coding
An implementation that implements a cache for the NameCoder that escapes the underscores.
XML Friendly Coding
The default implementation of XStream using a StringBuilder and a cache, encoding any character that is invalid for XML names. It implements also the underscore escaping for compatibility reasons with XML created by earlier versions of XStream.