As we just explained, to get the code-sharing effect, you should preload the code before the child processes get spawned. The right place to preload modules is at server startup.

You can use the PerlRequireand PerlModule directives to load commonly used modules such as CGI.pm and DBI when the server is started. On most systems, server children will be able to share the code space used by these modules. Just add the following directives into httpd.conf:

PerlModule CGI
PerlModule DBI

An even better approach is as follows. First, create a separate startup file. In this file you code in plain Perl, loading modules like this:

use DBI ( );
use Carp ( );
1;

(When a module is loaded, it may export symbols to your package namespace by default. The empty parentheses ( ) after a module's name prevent this. Don't forget this, unless you need some of these in the startup file, which is unlikely. It will save you a few more kilobytes of memory.)

Next, require( ) this startup file in httpd.conf with the PerlRequire directive, placing the directive before all the other mod_perl configuration directives:

PerlRequire /path/to/startup.pl

As usual, we provide some numbers to prove the theory. Let's conduct a memory-usage test to prove that preloading reduces memory requirements.

To simplify the measurement, we will use only one child process. We will use these settings in httpd.conf:

MinSpareServers 1
MaxSpareServers 1
StartServers 1
MaxClients 1
MaxRequestsPerChild 100

We are going to use memuse.pl (shown in Example 10-8), an Apache::Registry script that consists of two parts: the first one loads a bunch of modules (most of which aren't going to be used); the second reports the memory size and the shared memory size used by the single child process that we start, and the difference between the two, which is the amount of unshared memory.

Example 10-8. memuse.pl

use strict;
use CGI ( );
use DB_File ( );
use LWP::UserAgent ( );
use Storable ( );
use DBI ( );
use GTop ( );

my $r = shift;
$r->send_http_header('text/plain');
my $proc_mem = GTop->new->proc_mem($$);
my $size  = $proc_mem->size;
my $share = $proc_mem->share;
my $diff  = $size - $share;
printf "%10s %10s %10s\n", qw(Size Shared Unshared);
printf "%10d %10d %10d (bytes)\n", $size, $share, $diff;

First we restart the server and execute this CGI script with none of the above modules preloaded. Here is the result:

Size     Shared   Unshared
4706304  2134016  2572288 (bytes)

Now we take the following code:

use strict;
use CGI ( );
use DB_File ( );
use LWP::UserAgent ( );
use Storable ( );
use DBI ( );
use GTop ( );
1;

and copy it into the startup.pl file. The script remains unchanged. We restart the server (now the modules are preloaded) and execute it again. We get the following results:

Size     Shared   Unshared
4710400  3997696  712704 (bytes)

Let's put the two results into one table:

Preloading    Size   Shared  Unshared
---------------------------------------------
Yes        4710400  3997696    712704 (bytes)
No         4706304  2134016   2572288 (bytes)
---------------------------------------------
Difference    4096  1863680  -1859584

You can clearly see that when the modules weren't preloaded, the amount of shared memory was about 1,864 KB smaller than in the case where the modules were preloaded.

Assuming that you have 256 MB dedicated to the web server, if you didn't preload the modules, you could have 103 servers:

268435456 = X * 2572288 + 2134016

X = (268435456 - 2134016) / 2572288 = 103

(Here we have used the formula that we devised earlier in this chapter.)

Now let's calculate the same thing with the modules preloaded:

268435456 = X * 712704 + 3997696

X = (268435456 - 3997696) / 712704 = 371

You can have almost four times as many servers!!!

Remember, however, that memory pages get dirty, and the amount of shared memory gets smaller with time. We have presented the ideal case, where the shared memory stays intact. Therefore, in use, the real numbers will be a little bit different.

Since you will use different modules and different code, obviously in your case it's possible that the process sizes will be bigger and the shared memory smaller, and vice versa. You probably won't get the same ratio we did, but the example certainly shows the possibilities.