-
-
Save trwyant/d96fb327b6fe08f774e0 to your computer and use it in GitHub Desktop.
Stupid state trick
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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