Fast start-up: the daemon#

Every pperl invocation normally pays the full start-up bill: process creation, interpreter initialisation, and - the expensive part for real programs - compiling your script and every module it uses. The daemon lets repeated invocations of the same script skip that bill: a resident process keeps a fully compiled image of the script, and each new run is a fork of that image that goes straight to execution.

The feature is strictly opt-in, per invocation. Nothing changes for plain pperl script.pl, and whenever the daemon cannot serve a request exactly, pperl silently falls back to a normal cold run. Correctness never depends on the daemon; only speed does.

Quick start#

pperl --daemon=start                 # start the daemon (background)

PPERL_DAEMON=2 pperl report.pl       # first run: compiles, caches
PPERL_DAEMON=2 pperl report.pl       # every further run: fork + run

pperl --daemon=status                # what is cached, what is live
pperl --daemon=stop                  # shut it down

The first opted-in run of a script builds its template: the daemon compiles the script (including all used modules and BEGIN blocks) and keeps the result resident. Every later run of the same script forks the template and only executes.

When it pays#

The saving is your script’s compile time: parsing plus module loading plus BEGIN work. For a script that loads a handful of modules this is routinely the dominant part of start-up, and a template run can be several times faster end to end. For a trivial script that loads nothing, the saving roughly equals the daemon’s own overhead and the result is a wash - never a meaningful loss.

As everywhere in this guide: measure your own script, on your own machine, before drawing conclusions - see Measuring.

What running from a template means#

A template run executes your script’s run phase only. Compilation already happened, once, when the template was built. pperl guarantees that the following are fresh and correct for every run:

  • @ARGV, %ENV, the current directory, umask

  • standard input, output and error (your redirections and pipes)

  • the process id $$

  • exit codes, and death by signal (a killed script kills the invoking pperl the same way)

  • the __DATA__ handle - each run reads it from the start, independently of other runs

What is not re-done is anything your code did at compile time. BEGIN blocks, and module code executed at use time, ran once when the template was built; their side effects are part of the image every run inherits. Consequences you own when you opt a script in:

  • $^T (the script start time) and the random-seed state date from template creation. If your script cares, re-stamp them at the start of the run phase:

    INIT { $^T = time; srand(); }
    
  • Values read from %ENV at compile time (inside BEGIN or at use time) were the values of the run that built the template. Read configuration at run time, or in an INIT block, if it must track the invoking environment.

  • File handles, sockets or database connections opened at compile time are shared with every run. Open connections at run time - the same rule long established for preloading application servers.

If a script cannot satisfy this contract, simply do not opt it in; PPERL_DAEMON=2 is per invocation, not global.

Editing the script, and the cache#

The daemon notices when the script file itself changes and rebuilds the template automatically on the next run - editing your script never serves you a stale image.

Edits to modules the script loads are not detected automatically. After changing a module, drop the affected template by hand:

pperl --daemon-reset=report.pl   # forget this script's template
pperl --daemon-reset             # forget all templates

Templates are a bounded cache: at most --daemon-max-scripts of them (default 100), evicted least-recently-used, and each expires after the daemon’s idle timeout (--daemon-idle, default 900 seconds).

When pperl falls back to a cold run#

The daemon refuses - and the client falls back to a normal run, silently and correctly - whenever it could not reproduce the invocation exactly:

  • no daemon is running for this pperl binary

  • environment variables that shape interpreter start-up differ from the daemon’s (PERL_HASH_SEED, PERL_PERTURB_KEYS, PERL_RAND_SEED, PERL_INTERNAL_RAND_SEED, PERL_UNICODE, LANG, any LC_*, any MALLOC_*)

  • the command-line flags differ from the ones the template was built with (a template stores one configuration)

  • the template is still being built by a concurrent first run

  • the invocation is a -e one-liner (templates cache script files)

  • --interactive or --stats is in effect

Security and identity#

The daemon serves exactly one user: its socket lives in a directory with owner-only permissions and every connection’s peer user id is verified. It also serves exactly one build of pperl: the socket is keyed to the binary’s identity (pperl --build-id prints it), so after a pperl upgrade the old daemon is simply never contacted again and exits on its idle timeout.

Limitations#

  • Job control is approximate: the running script is a child of the daemon, not of your shell. Signals you send to pperl (including Ctrl-C) are forwarded to the script, but Ctrl-Z suspends only the foreground pperl, not the script process.

  • One template per script path, holding one flag configuration. Invoking the same script with different flags falls back to a cold run rather than rebuilding the template.

  • PPERL_DAEMON=1 (or --via-daemon) exists as a reduced mode that forks a pre-initialised interpreter but still compiles the script every run. It saves almost nothing and is mainly useful for testing the daemon itself; use PPERL_DAEMON=2 for the real benefit.