XML, the Perl Way

Comments on "Performance Comparison Between SAX XML::Filter::Dispatcher and XML::Twig"

These comments refer to Tom Anderson's Performance Comparison Between SAX XML::Filter::Dispatcher and XML::Twig article.

It's an interesting article. I especially never realized that returning a short (1) value from a handler could impact performances that much (see difference between test 2 and test 3 below).

The code presented is quite simple, it could be slightly simplified by replacing $_[1] by simply $_ in handlers (from the doc "$_ is also set to the element, so it is easy to write inline handlers like para => sub { $_->change_gi( 'p'); }.

There is one problem with the code though: using $_[1]->{att}->{val}: attributes should be accessed using the provided accessor method $_[1]->att( 'val'). This impacts performances, but the faster syntax is not garanteed to work in a future version of the module (for example if I decide to pool attribute values, of course I might implement it as a tied-hash, but there is no garantee for that in the documentation of the module).

I tried to squeeze some additionnal speed on the example, using twig_roots so the tree is not build for most elements. In this case this kinda defeats the purpose of XML::Twig and turns it essentially into a rather thin layer on top of XML::Parser, but at least it takes care of dispatching the callbacks without using "large case statements".

The code for the most interesting test (test 3) is this:

#!/usr/bin/perl
#
# Program to test performance of XML::Twig
# Tom Anderson + mirod
# Thu Jan 16 22:49:24 PST 2003 + Friday March 7 2003
# same as test_perf2 except handlers return 1
#
use strict;
use warnings;
use diagnostics;

use XML::Twig;
use File::Slurp;

my $VERSION=0.02;

my $out_file= shift( @ARGV) || "$0.out";

my $less_memory=1;
my $out="";
my $xml= XML::Twig->new(

# start_tag_handlers are called when the start tag is found
# if the element is outside of the twig_roots then parameters are similar to XML::Parser's:
# the twig, the gi and a hash of attributes
start_tag_handlers => 
  {
    '/TRACES/NET/WIR' => sub
    { my( $t, $gi, %atts)= @_;
      $out .= 'WIR '.  $atts{numseg}  .' '.  $atts{startx}  .' '.  $atts{starty}  .' '.
                       $atts{termx}   .' '.  $atts{termy}   .' '.  $atts{optgroup}."\n";
      1;
    },
    '/TRACES/NET' => sub
    { my( $t, $gi, %atts)= @_;
      $out .= "# NET '".  $atts{name}."'\n";
      1;
    },
    '/TRACES/UNITS' => sub
    { my( $t, $gi, %atts)= @_;
      $out .= 'UNITS '.  $atts{val}."\n";
      1;
    },

    '/TRACES/STFIRST' => sub
    { my( $t, $gi, %atts)= @_;
      $out .= 'ST '.  $atts{maxx}    .' '.  $atts{maxy}    .' '.  $atts{maxroute}.' '.  $atts{numconn} ."\n";
      1;
    },

    '/TRACES/XRF' => sub
    { my( $t, $gi, %atts)= @_;
      $out .= 'XRF  '.  $atts{num} .' '.  $atts{name}."\n";
      1;
    },

    '/TRACES/NET/WIR/SEG' => sub
    { my( $t, $gi, %atts)= @_;
      $out .= 'SEG '.  $atts{x}    .' '.  $atts{y}    .' '.  $atts{lay}  .' '.  $atts{width}."\n";
      1;
    },

    '/TRACES/NET/GUI' => sub
    { my( $t, $gi, %atts)= @_;
      $out .= 'GUI '.  $atts{startx}  .' '.  $atts{starty}  .' '.  $atts{startlay}.' '.
                       $atts{termx}   .' '.  $atts{termy}   .' '.  $atts{termlay} .' '.  $atts{optgroup}."\n";
      1;
    },

    '/TRACES/STLAST' => sub
    { my( $t, $gi, %atts)= @_;
      $out .= 'ST '.  $atts{checkstat}  .' '.  $atts{numcomplete}.' '.  $atts{numinc}     .' '.
                      $atts{numunroute} .' '.  $atts{numnotrace} .' '.  $atts{numfill}    ."\n";
      1;
    },
  },

# the twig will be built only for those elements
twig_roots =>
  {
   '/TRACES/HEADER' => sub
    {
      $out .= $_->text;
      $_[0]->purge if $less_memory; # not really useful in this case, there is only 1, small  HEADER element
      1;
    },
  },
);

$xml->parsefile('traces.xml');
write_file( $out_file, $out);

All tests: sax_twig_comments.tar.gz, using data from http://tomacorp.com/perl/xml/traces.xml (unpack, save traces.xml in the sax_twig_comments directory and run with run_test_perf)

Here are the results:

Test 1 - original version (test_perf1)
  2 wallclock secs (0.00 usr 0.00 sys + 1.63 cusr 0.09 csys = 1.72 CPU)
Test 2 - using twig_roots (test_perf2)
  4 wallclock secs (0.00 usr 0.00 sys + 2.76 cusr 0.97 csys = 3.73 CPU)
Test 3 - twig_roots + return 1 (test_perf3)
  0 wallclock secs (0.01 usr 0.00 sys + 0.72 cusr 0.03 csys = 0.76 CPU)
Test 4 - twig_roots + return undef (test_perf4)
  1 wallclock secs (0.00 usr 0.00 sys + 0.71 cusr 0.05 csys = 0.76 CPU)
Test 5 - handlers on element name (test_perf5)
  1 wallclock secs (0.00 usr 0.00 sys + 0.74 cusr 0.01 csys = 0.75 CPU)
Test 6 - same with partial path (test_perf6)
  1 wallclock secs (0.00 usr 0.00 sys + 0.70 cusr 0.04 csys = 0.74 CPU)

Note that results for test 5 and 6 vary, sometimes one is faster than the other, sometimes it's the opposite.


© Michel Rodriguez - 2003