#!/usr/bin/perl ############################################################## # This script was born after I realized that I had 10+ # `use Module::Name` statements at the top of my script and I # noticed that startup time was getting slower. # # I've determined that startup time less than 40 ms is largely # considered "instant" to the average person. # # Scott Baker - scott@perturb.org - 2022-03-11 ############################################################## use v5.16; use strict; use warnings; use Time::HiRes qw(time); # List of modules to test... add your favorites here to # see how they affect script startup time my @modules = qw( strict warnings Getopt::Long Path::Tiny JSON::PP Data::Dumper Data::Dump::Color File::Basename Time::HiRes English Term::ANSIColor Time::Piece Dump::Krumo ); if (@ARGV) { @modules = @ARGV; } ############################################################## # Just some semi-real-world Perl code my $test_cmd; # Borrowed from https://stackoverflow.com/questions/14667543/code-in-perl-for-pi-calculation $test_cmd = 'my $x = .5; my $pi = 0; $pi = (2 / $x++) - $pi for 0 .. 200000; print "Pi is: $pi\n";'; #$test_cmd = 'print "Time is: " . time();'; # Calculate completion time with NO modules loaded print "Establishing baseline... "; my $baseline = run_x_times(50, "perl -e '$test_cmd'", 0); print "\b" . color('white') . $baseline . color() . " ms\n\n"; my $max = max_length(@modules); # Add each module and run the code again foreach my $mod (@modules) { my $cmd = "perl -M$mod -e '$test_cmd'"; my $avg = run_x_times(50, $cmd); if ($avg == -1) { print color("red") . "Skipping:" . color() . " $mod is not installed\n"; next; } # Calculate how much additional time is added by 'use module' my $diff = sprintf("%4.1f", $avg - $baseline); # Minor system fluctuations were resulting in some -0.1 results #if ($diff < 0) { $diff = 0; } # Color code the output if ($diff < 5) { $diff = color("green") . $diff . color(); } elsif ($diff < 10) { $diff = color("yellow") . $diff . color(); } elsif ($diff < 15) { $diff = color("orange") . $diff . color(); } elsif ($diff >= 15) { $diff = color("red") . $diff . color(); } print "\b\b\b"; # Backspace #print "'use $mod' adds $diff ms to startup time\n"; printf("%${max}s adds %s ms to startup time\n", $mod, $diff); } ############################################################### ## Functions ############################################################### sub max_length { my $max = 0; foreach my $item (@_) { my $len = length($item); if ($len > $max) { $max = $len; } } return $max; } sub run_x_times { my ($num, $cmd, $quiet) = @_; my @times; # Run command X times and calculate the MS for each run for (my $i = 0; $i < $num; $i++) { my $start = time(); my $out = `$cmd 2>&1`; my $exit = $? >> 8; my $total = (time() - $start) * 1000; if ($exit != 0) { return -1; } push(@times, $total); if (!$quiet) { spinner(); } } # Throw away the first one to let things get warmed up/cached @times = splice(@times, 1); if (!$quiet) { spinner(); } my $ret = sprintf("%.1f", average(@times)); return $ret; } # Calculate the average of items in an array sub average { my $ret = 0; foreach (@_) { $ret += $_; } my $count = scalar(@_); $ret = $ret / $count; return $ret; } # Debug print variable using either Data::Dump::Color (preferred) or Data::Dumper # Creates methods k() and kd() to print, and print & die respectively BEGIN { if (eval { require Data::Dump::Color }) { *k = sub { Data::Dump::Color::dd(@_) }; } else { require Data::Dumper; *k = sub { print Data::Dumper::Dumper(\@_) }; } sub kd { k(@_); printf("Died at %2\$s line #%3\$s\n",caller()); exit(15); } } # String format: '115', '165_bold', '10_on_140', 'reset', 'on_173', 'red', 'white_on_blue' sub color { my $str = shift(); # If we're NOT connected to a an interactive terminal don't do color if (-t STDOUT == 0) { return ''; } # No string sent in, so we just reset if (!length($str) || $str eq 'reset') { return "\e[0m"; } # Some predefined colors my %color_map = qw(red 160 blue 27 green 34 yellow 226 orange 214 purple 93 white 15 black 0); $str =~ s|([A-Za-z]+)|$color_map{$1} // $1|eg; # Get foreground/background and any commands my ($fc,$cmd) = $str =~ /^(\d{1,3})?_?(\w+)?$/g; my ($bc) = $str =~ /on_(\d{1,3})$/g; # Some predefined commands my %cmd_map = qw(bold 1 italic 3 underline 4 blink 5 inverse 7); my $cmd_num = $cmd_map{$cmd // 0}; my $ret = ''; if ($cmd_num) { $ret .= "\e[${cmd_num}m"; } if (defined($fc)) { $ret .= "\e[38;5;${fc}m"; } if (defined($bc)) { $ret .= "\e[48;5;${bc}m"; } return $ret; } sub spinner { my @chars = ("-", "\\", "|", "/"); my $backsp = "\b"; state $cur_key = 0; my $spinner_lag = $_[0] if $_[0]; if ($cur_key == @chars) { $cur_key = 0; } if ($spinner_lag) { my $i; if ($i == $spinner_lag) { print $backsp . $chars[$cur_key]; $i = 1; } else { $i++; } } else { print $backsp . $chars[$cur_key]; } $cur_key++; }