Faster xml deserialisation on JRuby

For the last day or so, I've been looking at XML deserilisation performance at work. When dealing with REST/POX, a respectable fraction of the time is spent in serilising and deserialising xml; consequently it's a fair target for performance analysis. Since I'm currently working on a multi-threaded Twitter library built using Wrest, I decided I might as well take a look at xml deserialisation performance under JRuby.

The Contenders

Wrest delegates XML deserilisation to ActiveSupport, which in turn supports one of three libraries - LibXML Ruby, Nokogiri and of course REXML. REXML is the slowest, buggiest of the three and is pure Ruby. Both LibXML and Nokogiri use the native libxml2 libraries; however LibXML is not available on JRuby whereas Nokogiri is. Nokogiri also has an as yet unreleased version that does not use the JNA based JRuby FFI implementation and is expected to be faster. Build instructions for the non FFI Nokogiri are available here.

REXML can however be enhanced by including JREXML which uses the java xpp3 libs and claims a 10x performance improvemnt.

Thus we have four contenders:
  • Vanilla REXML
  • REXML enhanced by JREXML
  • FFI Nokogiri
  • Non FFI nokogiri

The Test Environment
  • jruby -v reads jruby 1.4.0dev (ruby 1.8.7p174) (2009-08-05 619cebe) (Java HotSpot(TM) 64-Bit Server VM 1.6.0_13) [x86_64-java])
  • I'm using a 2.2GHz 2008 MacBook Pro running Leopard
  • The benchmark is a simple Hash.from_xml. It's canned as a Wrest rake task. The full command is jruby -S rake -J-server benchmark:deserialise_xml
  • JREXML 0.5.3 and Nokogiri 1.3.3 (the non-FFI build is also 1.3.3, revision fb7e9bb6, from the origin/java branch of tenderlove/nokogiri)

The Numbers

Vanilla REXML
Deserialising using ActiveSupport::XmlMini_REXML
Rehearsal -------------------------------------------------
Hash.from_xml 11.831000 0.000000 11.831000 ( 11.831000)
--------------------------------------- total: 11.831000sec

user system total real
hash.from_xml 5.475000 0.000000 5.475000 ( 5.474000)

REXML + JREXML
Detected JRuby, JREXML loaded.
Deserialising using ActiveSupport::XmlMini_REXML
Rehearsal -------------------------------------------------
Hash.from_xml 11.323000 0.000000 11.323000 ( 11.323000)
--------------------------------------- total: 11.323000sec

user system total real
Hash.from_xml 5.436000 0.000000 5.436000 ( 5.436000)

FFI Nokogiri
Deserialising using ActiveSupport::XmlMini_Nokogiri
Rehearsal -------------------------------------------------
Hash.from_xml 9.468000 0.000000 9.468000 ( 9.468000)
---------------------------------------- total: 9.468000sec

user system total real
Hash.from_xml 3.876000 0.000000 3.876000 ( 3.876000)

Non FFI Nokogiri
Deserialising using ActiveSupport::XmlMini_Nokogiri
Rehearsal -------------------------------------------------
Hash.from_xml 5.956000 0.000000 5.956000 ( 5.956000)
---------------------------------------- total: 5.956000sec

user system total real
Hash.from_xml 2.123000 0.000000 2.123000 ( 2.123000)


Conclusion
As expected, REXML was slow. Surprisingly though, JREXML didn't improve those numbers very much.
FFI Nokogiri was faster than REXML, but the JNA seems to have taken its toll - on MRI 1.8.6 the same benchmark runs in under 2s. Non FFI Nokogiri was the real win, though, taking deserialisation performance within spitting distance of the CRuby Nokogiri.

Note that Hash.from_xml does mess around a bit with the hash that the libraries produce and this might make the numbers different from directly using the xml libraries; however since this API is what I need to use with in Wrest (and Rails on JRuby, for that matter) these are the relative performance numbers I'm interested in seeing. If you're not using Rails (or Wrest, dare I say?) you may want to re-run this benchmark against the libraries directly without ActiveSupport mediating.

4 comments:

Gerald Boersma said...

Another approach for handling XML that works well in JRuby is to use the underlying XML functionality in the Java SDK. It's pretty easy to write a thin wrapper in Ruby that invokes the underlying SDK. Check out http://geraldboersma.blogspot.com/2009/07/processing-xml-with-jruby.html for some example code.

I have seen significant performance improvement for what I was trying to do. I would be interested in hearing how well this works for you.

Anonymous said...

You may want to look at vtd-xml as the state of the art in XML processing, consuming far less memory than DOM

http://vtd-xml.sf.net

Michal Hantl said...

The time of REXML and REXML+JREXML seem far too similar.

I found this http://pastie.org/85850.

You may want to check the benchmark or something.

XPP is one of the fastest xml parsers so the problem might be somewhere else..

Anonymous said...

It is very interesting for me to read this article. Thanks the author for it. I like such topics and anything connected to them. BTW, try to add some pics :).