Measure Perl Hot Spots with Benchmark and Safe Profiling
A script that took two seconds last month and takes twenty now, with nobody admitting to changing the code, is exactly what perlperf is for. You will measure a working program, find where it actually spends its time, make one controlled change, and prove the result is still correct. The local perlperf manual is a guide, not an executable command. These examples use Perl 5.38.2 and the perl-doc package installed on this machine.
The route
Jump straight to the step you need, or tick off Done means at the end.
Allow about 20 minutes for a small script, plus however long your real test data takes. You need Perl, a test or sample input, and a version-control checkout if you are changing application code.
Warning
Do not optimise an untested program. A faster incorrect result is still a failure, just a quicker one.
1. Confirm the tools and make a baseline
Check the interpreter and documentation package with ordinary, read-only commands:
$ perl -v
This is perl 5, version 38, subversion 2 (v5.38.2)
$ dpkg-query -W -f='${Package} ${Version}\n' perl-doc
perl-doc 5.38.2-3.2ubuntu0.6
$ command -v time
/usr/bin/time
Run the program once with a known input and record its output. Then measure the same invocation with the shell's time utility:
$ time perl bin/report.pl data/sample.txt > /tmp/report.before
real 0m0.005s
user 0m0.003s
sys 0m0.001s
Your figures will differ. Three numbers matter here:
- real is elapsed wall-clock time, including any waiting.
- user is CPU time the program spent in user space.
- sys is CPU time spent in the kernel on the program's behalf.
Keep the input, command, Perl version and output together, and repeat the run: other processes on the box can nudge the timings around.
Checkpoint
The baseline is only useful if the program's output is correct and the command is repeatable. Save a checksum or a test result before you edit anything:
$ cksum /tmp/report.before
3044173777 52029194 /tmp/report.before
$ perl bin/report.pl data/sample.txt > /tmp/report.repeat
$ cksum /tmp/report.repeat
That sample checksum is not a universal expected value, it is only an example of recording your own result.
2. Compare a small operation with Benchmark
Reach for the Benchmark module when you are comparing one focused piece of Perl code. Give each candidate the same inputs and return a value from the code under test, so the comparison measures real work rather than a shortcut the optimiser noticed:
$ perl -MBenchmark=timethese -e '
my $source = "0123456789abcdefghijklmnopqrstuvwxyz";
timethese(100000, {
regex => sub { my $s = $source; $s =~ s/[aeiou]/x/g; return $s },
tr => sub { my $s = $source; $s =~ tr/aeiou/xxxxx/; return $s },
});'
Benchmark: timing 100000 iterations of regex, tr...
The report gives wall-clock and CPU measurements, an iteration count, and a calculated rate. The exact numbers depend on this machine and can be too small to read cleanly on a short test, so increase the iteration count until the result settles, keeping each candidate equivalent. Here tr is a fixed character translation while the regular expression is more flexible, so a faster result is not automatically the right replacement if it drops behaviour you need.
Checkpoint
Run the benchmark twice and compare the ordering, not just one rate. If the order changes or the figures are noisy, use more iterations or a more representative input. Never infer an application-wide improvement from a microbenchmark alone, that is how a two-line win turns into a production regression.
3. Find the hot path with a profiler
Bring in a profiler only after you have a repeatable baseline. The local manual points to the Perl debugger switch paired with Devel::NYTProf, and reports from nytprofhtml or nytprofcsv. Check first whether that module and its report tools are even installed:
$ perl -MDevel::NYTProf -e 'print "$Devel::NYTProf::VERSION\n"'
Can't locate Devel/NYTProf.pm in @INC ...
$ command -v nytprofhtml || true
On this installation, NYTProf is not available, so do not copy the profiling command and expect it to work. Installing extra packages is an administrator or project-owner decision outside this guide, and this same check stops you confusing a missing module with a bug in your own program.
If your approved environment already provides NYTProf, profile a normal workload into a separate working directory:
$ mkdir -p /tmp/perlperf-run
$ cd /tmp/perlperf-run
$ perl -d:NYTProf /path/to/bin/report.pl /path/to/data/sample.txt > report.out
$ nytprofhtml
Warning
Use a disposable directory. The profiler writes a report database, normally nytprof.out, and the HTML command builds a report tree that can expose file names, query text or other sensitive details. Keep it private and remove it once you are done, and do not profile production traffic casually since instrumentation adds overhead and eats storage.
Read the report from the top down. A subroutine with high exclusive time is doing the work itself; high inclusive time can just mean it calls something expensive. A line or subroutine called many times can be a better target than one rare, slow call. Confirm what you see with a second representative run before touching any code.
4. Change one thing and verify the same task
Make one narrow edit, in version control. The perlperf guidance flags the usual trade-offs: a hash lookup can beat repeatedly scanning a list, the built-in cmp and <=> sort operators are usually cheaper than a custom comparison, and caching sort keys can cost memory and an extra pass to build. Treat these as hypotheses, not rules, and measure your own data.
Before replacing the old code, preserve its output and run the test suite:
$ git diff --check
$ prove -lr t
All tests successful.
$ time perl bin/report.pl data/sample.txt > /tmp/report.after
$ cksum /tmp/report.before /tmp/report.after
3044173777 52029194 /tmp/report.before
3044173777 52029194 /tmp/report.after
Warning
If the checksums differ, stop. A changed output might be an intended correction, but it is not evidence of a safe performance improvement. Inspect the difference and restore the change through your normal version-control workflow if it was not intended, and do not reach for a destructive reset while you still have uncommitted work you need.
Run the same measured command several times after the edit, comparing like with like: same input, environment, output destination and warm-up conditions. Keep both the timing and the correctness result. A lower real time paired with a much higher sys time can mean you moved work into the kernel rather than removed it.
5. Avoid the common measurement traps
- Do not optimise before correctness and tests. Otherwise a benchmark just rewards a broken shortcut.
- Do not change several functions at once. You lose the link between the edit and the measured result.
- Do not compare different inputs, output paths or debug settings. Logging and diagnostics can dominate a short run.
- Do not trust a tiny benchmark result. The module itself warns when there are too few iterations for a reliable count.
- Do not leave verbose debug logging on while measuring. Building a message can cost time even when the logger discards it afterwards, so guard expensive diagnostic work before it ever reaches the logger.
- Do not run a profiler as root just to get a report. Use the least privilege needed to read the input and write the output.
Done means
- Baseline recorded: the program had a correct, repeatable baseline and a saved input.
- Microbenchmark sound: any Benchmark run used equal work and enough iterations to settle.
- Hot path confirmed: a profiler, where available, pointed at a measured hot path rather than a guess.
- One change at a time: only one edit separated any two comparisons.
- Tests and output pass: the edited program passed its tests, produced the same required output, and beat the baseline on the measured workload.
- Cleanup done: temporary reports and sensitive inputs stayed private, and no production service was touched during measurement.