13.2 Apache::args Versus Apache::Request::param Versus CGI::param

Apache::args, Apache::Request::param, and CGI::param are the three most common ways to process input arguments in mod_perl handlers and scripts. Let's write three Apache::Registry scripts that use Apache::args, Apache::Request::param, and CGI::param to process a form's input and print it out. Notice that Apache::args is considered identical to Apache::Request::param only when you have single-valued keys. In the case of multi-valued keys (e.g., when using checkbox groups), you will have to write some extra code. If you do a simple:

my %params = $r->args;

only the last value will be stored and the rest will collapse, because that's what happens when you turn a list into a hash. Assuming that you have the following list:

(rules => 'Apache', rules => 'Perl', rules => 'mod_perl')

and assign it to a hash, the following happens:

$hash{rules} = 'Apache';
$hash{rules} = 'Perl';
$hash{rules} = 'mod_perl';

So at the end only the following pair will get stored:

rules => 'mod_perl'

With CGI.pm or Apache::Request, you can solve this by extracting the whole list by its key:

my @values = $q->param('rules');

In addition, Apache::Request and CGI.pm have many more functions that ease input processing, such as handling file uploads. However, Apache::Request is theoretically much faster, since its guts are implemented in C, glued to Perl using XS code.

Assuming that the only functionality you need is the parsing of key-value pairs, and assuming that every key has a single value, we will compare the almost identical scripts in Examples 13-3, 13-4, and 13-5 by trying to pass various query strings.

Example 13-3. processing_with_apache_args.pl
use strict;
my $r = shift;
$r->send_http_header('text/plain');

my %args = $r->args;
print join "\n", map {"$_ => $args{$_}" } keys %args;
Example 13-4. processing_with_apache_request.pl
use strict;
use Apache::Request ( );
my $r = shift;
my $q = Apache::Request->new($r);
$r->send_http_header('text/plain');

my %args = map {$_ => $q->param($_) } $q->param;
print join "\n", map {"$_ => $args{$_}" } keys %args;
Example 13-5. processing_with_cgi_pm.pl
use strict;
use CGI;
my $r = shift;
my $q = new CGI;
$r->send_http_header('text/plain');

my %args = map {$_ => $q->param($_) } $q->param;
print join "\n", map {"$_ => $args{$_}" } keys %args;

All three scripts and the modules they use are preloaded at server startup in startup.pl:

use Apache::RegistryLoader ( );
use CGI ( );
CGI->compile('param');
use Apache::Request ( );

# Preload registry scripts
Apache::RegistryLoader->new->handler(
                          "/perl/processing_with_cgi_pm.pl",
               "/home/httpd/perl/processing_with_cgi_pm.pl"
                  );
Apache::RegistryLoader->new->handler(
                          "/perl/processing_with_apache_request.pl",
               "/home/httpd/perl/processing_with_apache_request.pl"
                  );
Apache::RegistryLoader->new->handler(
                          "/perl/processing_with_apache_args.pl",
               "/home/httpd/perl/processing_with_apache_args.pl"
                  );
1;

We use four different query strings, generated by:

my @queries = (
    join("&", map {"$_=" . 'e' x 10}  ('a'..'b')),
    join("&", map {"$_=" . 'e' x 50}  ('a'..'b')),
    join("&", map {"$_=" . 'e' x 5 }  ('a'..'z')),
    join("&", map {"$_=" . 'e' x 10}  ('a'..'z')),
);

The first string is:

a=eeeeeeeeee&b=eeeeeeeeee

which is 25 characters in length and consists of two key/value pairs. The second string is also made of two key/value pairs, but the values are 50 characters long (a total of 105 characters). The third and fourth strings are each made from 26 key/value pairs, with value lengths of 5 and 10 characters respectively and total lengths of 207 and 337 characters respectively. The query_len column in the report table is one of these four total lengths.

We conduct the benchmark with a concurrency level of 50 and generate 5,000 requests for each test. The results are:

---------------------------------------------
name   val_len pairs query_len |  avtime  rps
---------------------------------------------
apreq     10      2       25   |    51    945
apreq     50      2      105   |    53    907
r_args    50      2      105   |    53    906
r_args    10      2       25   |    53    899
apreq      5     26      207   |    64    754
apreq     10     26      337   |    65    742
r_args     5     26      207   |    73    665
r_args    10     26      337   |    74    657
cgi_pm    50      2      105   |    85    573
cgi_pm    10      2       25   |    87    559
cgi_pm     5     26      207   |   188    263
cgi_pm    10     26      337   |   188    262
---------------------------------------------

where apreq stands for Apache::Request::param( ), r_args stands for Apache::args( ) or $r->args( ), and cgi_pm stands for CGI::param( ).

You can see that Apache::Request::param and Apache::args have similar performance with a few key/value pairs, but the former is faster with many key/value pairs. CGI::param is significantly slower than the other two methods.

These results also suggest that the processing gets progressively slower as the number of key/value pairs grows, but longer lengths of the key/value pairs have less of a slowdown impact. To verify that, let's use the Apache::Request::param method and first test several query strings made of five key/value pairs with value lengths growing from 10 characters to 60 in steps of 10:

my @strings = map {'e' x (10*$_)} 1..6;
my @ae = ('a'..'e');
my @queries = ( );
for my $string (@strings) {
    push @queries, join "&", map {"$_=$string"} @ae;
}

The results are:

-----------------------------------
val_len query_len    |  avtime  rps
-----------------------------------
  10       77        |    55    877
  20      197        |    55    867
  30      257        |    56    859
  40      137        |    56    858
  50      317        |    56    857
  60      377        |    58    828
-----------------------------------

Indeed, the length of the value influences the speed very little, as we can see that the average processing time almost doesn't change as the length of the value grows.

Now let's use a fixed value length of 10 characters and test with a varying number of key/value pairs, from 2 to 26 in steps of 5:

my @az = ('a'..'z');
my @queries = map { join("&", map {"$_=" . 'e' x 10 } @az[0..$_]) } 
    (1, 5, 10, 15, 20, 25);

The results are:

-------------------------------
pairs  query_len |  avtime  rps
-------------------------------
  2       25     |    53    906
  6       77     |    55    869
 12      142     |    57    838
 16      207     |    61    785
 21      272     |    64    754
 26      337     |    66    726
-------------------------------

Now by looking at the average processing time column, we can see that the number of key/value pairs makes a significant impact on processing speed.



    Part I: mod_perl Administration
    Part II: mod_perl Performance
    Part VI: Appendixes