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.
Post a Comment