Skip to content

Instantly share code, notes, and snippets.

@trwyant

trwyant/state Secret

Created February 4, 2016 23:22
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save trwyant/d96fb327b6fe08f774e0 to your computer and use it in GitHub Desktop.
Save trwyant/d96fb327b6fe08f774e0 to your computer and use it in GitHub Desktop.
Stupid state trick
#!/usr/bin/env perl
use 5.010;
use strict;
use warnings;
use Benchmark qw{ :all };
use Getopt::Long 2.33 qw{ :config auto_version };
use Pod::Usage;
our $VERSION = '0.000_01';
=begin comment
BEGIN {
# This override of require() is used to demonstrate that it is only
# called once by test_state(), but every time by test_require().
# You want to comment out the whole BEGIN block before going for
# serious numbers of iterations.
*CORE::GLOBAL::require = sub {
my ( $module ) = @_;
my ( $pkg, $fn, $line ) = caller;
$fn eq $0
and say "Loading $module from $fn line $line";
return CORE::require( $module );
}
}
=end comment
=cut
my %opt = (
iterations => 10,
);
my %algorithm;
{
my $conditional;
%algorithm = (
conditional => sub {
$conditional
or require YAML;
$conditional = 1;
return;
},
null => sub {
return;
},
require => sub {
require YAML;
return;
},
state => sub {
state $dummy = require YAML;
return;
},
);
}
GetOptions( \%opt,
qw{ iterations=i },
'algorithm=s@' => sub {
my ( $name, $val ) = @_;
$algorithm{$val}
or die '-algorithm must be one of ', join( ', ', map {
"'$_'" } sort keys %algorithm ), "\n";
push @{ $opt{$name} ||= [] }, $val;
return;
},
help => sub { pod2usage( { -verbose => 2 } ) },
) or pod2usage( { -verbose => 0 } );
$opt{algorithm} ||= [ sort keys %algorithm ];
timethese( $opt{iterations}, {
map { $_ => $algorithm{$_} } @{ $opt{algorithm} }
}
);
__END__
=head1 TITLE
state - Demonstrate and time various Perl module loading algorithms
=head1 SYNOPSIS
state
state -algorithm null -algorithm state
state -iterations 100000000
state -help
state -version
=head1 OPTIONS
=head2 -algorithm
-algorithm null
This option specifies what module loading algorithm to benchmark. It can
be specified more than once. The valid algorithm names are documented
under L<ALGORITHMS|/ALGORITHMS>
The default is to benchmark all programmed algorithms, in ASCIIbetical
order.
=head2 -help
This option displays the documentation for this script. The script then
exits.
=head2 -iterations
-iterations 100000000
This option specifies how many times the tested subroutine is called.
The default is C<-iterations 10> which is sufficient for demonstration
purposes, but grossly insufficient for benchmarking.
=head2 -version
This option displays the version of this script. The script then exits.
=head1 DETAILS
This Perl script is used to explore the magnitude of the time saving
(if any) involved in various approaches to loading Perl modules at
execution time. It was originally written to investigate the behavior of
the L<state|/state> algorithm.
This code contains a commented-out override of the core C<require()>
subroutine, which can be uncommented to demonstrate how often
C<require()> is called by each algorithm. You do B<not> want this
override enabled if you are doing large numbers of iterations, because
of the volume of output that can be generated, and because of the extra
overhead imposed on algorithms that call C<require> once for each
invocation.
=head1 ALGORITHMS
The following algorithms for run-time module loading are supported. For
narrative convenience the algorithms are documented below as though they
were normal subroutines, but they are actually implemented as anonymous
subroutines in a hash keyed by algorithm name.
=head2 conditional
my $conditional;
sub test_conditional {
$conditional
or require YAML;
$conditional = 1;
return;
}
Something like this is the way to explicitly ensure C<require YAML> is
executed only once in pre-5.10 Perl.
=head2 null
sub test_null {
return;
}
This is included to give an estimate of the amount of time spent simply
calling the code being tested.
=head2 require
sub test_require {
require YAML;
return;
}
The C<require()> built-in is idempotent, so the C<YAML> module is only
loaded once however many times this code is executed. But C<require()>
is executed at every call.
=head2 state
sub test_state {
state $dummy = require YAML;
return;
}
THe C<state()> built-in was introduced in Perl 5.10. It creates a
lexical variable, but the assignment is only done the first time it is
encountered. It occurred to me that if the right-hand side of the
assignment were a C<require()>, then it would only be executed the first
time it was encountered.
=head1 RESULTS
Running 10**9 iterations of all four algorithms on a system with no
other user-requested load produced the following results:
Benchmark: timing 1000000000 iterations of conditional, null, require, state...
conditional: 48 wallclock secs (48.04 usr + 0.00 sys = 48.04 CPU) @ 20815986.68/s (n=1000000000)
null: 9 wallclock secs ( 8.45 usr + 0.01 sys = 8.46 CPU) @ 118203309.69/s (n=1000000000)
require: 70 wallclock secs (70.41 usr + -0.03 sys = 70.38 CPU) @ 14208581.98/s (n=1000000000)
state: 24 wallclock secs (22.92 usr + -0.01 sys = 22.91 CPU) @ 43649061.55/s (n=1000000000)
Deducting the time of the null algorithm from each of the others
produces:
require: 61 wallclock secs @ 16393442.62/s
conditional: 39 wallclock secs @ 25641025.64/s
state: 15 wallclock secs @ 66666666.67/s
Two conclusions appear to be reasonable:
=over
=item * Under the test conditions the C<state> algorithm is
significantly faster than just making use of C<require>'s idempotence.
If that is unavailable, an explicit conditional is still a win.
=item * Unless you anticipate truly staggering numbers of iterations,
the difference is probably not worth worrying about.
=back
=head1 AUTHOR
Thomas R. Wyant, III F<wyant at cpan dot org>
=head1 COPYRIGHT AND LICENSE
Copyright (C) 2016 by Thomas R. Wyant, III
This program is free software; you can redistribute it and/or modify it
under the same terms as Perl 5.10.0. For more details, see the Artistic
License 1.0 at
L<http://www.perlfoundation.org/artistic_license_1_0>, and/or the Gnu
GPL at L<http://www.gnu.org/licenses/old-licenses/gpl-1.0.txt>.
This program is distributed in the hope that it will be useful, but
without any warranty; without even the implied warranty of
merchantability or fitness for a particular purpose.
=cut
# ex: set textwidth=72 :
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment