Profiling Perl

Often we write perl scripts that loop a particular action. Not so often we write a perl script that is called in a loop by another tool. My script was attempting to do a http web login to emulate user traffic. However, I was not able to start more than 8 of these scripts a second.

Timing the script inside of perl was actually almost useless. Often you have a profiling marker like:

use Time::HiRes::time;
my $start = time;
# stuff
print "delta: ".(time - $start)."\n";

And that was just telling me I spent 4ms doing my stuff. And so where the heck did the other 108ms occur?

Here was another trick I started using to estimate latency: passing in the start time from the command line:

$ ./ --invoked `date +%s.%4N`

and I would parse it in the script:
my $start;

   "invoked=f" => \$start,
# stuff
print "delta: ".(time - $start)."\n";

Ah…there was my 114ms latency again. So startup of the script must be eating up ~100ms. Well, what could I remove? Anything I did not need to use obviously. Let’s comment out my strictitudinal use statements:

#use strict;
#use warnings;
#use diagnostics;
#use Carp;
#$SIG{ __DIE__ } = sub { Carp::confess( @_ ) };

Wow, I just gained 40ms! But can’t I do better than that? Ah hah, time to use a profiler. After a bit of research, I installed the NYTProf package.

I found some surprising slowdown spots:

  • Time::HiRes – took up to 10ms to load, using `date` takes about 300 microseconds
  • GetOpt::Long – pretty long load time, with a lot of dependencies, what I have now to parse ARGV is not as flexible, but it takes no time to start up.
  • use constant – took up to 45ms to load, about 20ms of that was loading Apparently it takes a lot of work to allow any utf8 character to be a legal script constant? Didn’t need it.
  • including ‘use strict’ — in all my own packages, removed them when I wasn’t debugging

Removing GetOpt::Long usage was pretty time consuming. I solved it in about 100 lines of code while still keeping something very close to the original GetOpt syntax, but turning it into a map. Kinda fun, but a bit nail-biting at the same time. Resulting function weighted in at under 100 microseconds.

Result? 6.6 milliseconds run time! I was able to operate the script upto to 68 times a second.