This is a live mirror of the Perl 5 development currently hosted at https://github.com/perl/perl5
bench.pl: add 'pre' and 'post' benchmark fields
authorDavid Mitchell <davem@iabyn.com>
Sat, 21 Oct 2017 22:53:03 +0000 (23:53 +0100)
committerDavid Mitchell <davem@iabyn.com>
Mon, 23 Oct 2017 10:52:03 +0000 (11:52 +0100)
These allow actions to be performed each time round the loop, just before
and after the benchmarked code, but without contributing to the timings.

For example to benchmark appending to a string, you need to reset the
string to a known state before each iteration, otherwise the string gets
bigger and bigger with each iteration:

    code => '$s = ""; $s .= "foo"',

but now you're measuring both the concat and an assign. To measure just
the concat, you can now do:

    pre  => '$s = ""',
    code => '$s .= "foo"',

Note the contrast with 'setup', which is only executed once, outside the
loop.

Porting/bench.pl
t/perf/benchmarks

index 9866f8a..303eee4 100755 (executable)
@@ -619,7 +619,7 @@ sub read_tests_file {
     # validate and process each test
 
     {
-        my %valid = map { $_ => 1 } qw(desc setup code);
+        my %valid = map { $_ => 1 } qw(desc setup code pre post);
         my @tests = @$ta;
         if (!@tests || @tests % 2 != 0) {
             die "Error: '$file' does not contain evenly paired test names and hashes\n";
@@ -846,19 +846,33 @@ sub process_executables_list {
 
 
 
-# Return a string containing perl test code wrapped in a loop
-# that runs $ARGV[0] times
+# Return a string containing a perl program which runs the benchmark code
+# $ARGV[0] times. If $body is true, include the main body (setup) in
+# the loop; otherwise create an empty loop with just pre and post.
+# Note that an empty body is handled with '1;' so that a completely empty
+# loop has a single nextstate rather than a stub op, so more closely
+# matches the active loop; e.g.:
+#   {1;}    => nextstate;                       unstack
+#   {$x=1;} => nextstate; const; gvsv; sassign; unstack
+# Note also that each statement is prefixed with a label; this avoids
+# adjacent nextstate ops being optimised away
 
 sub make_perl_prog {
-    my ($test, $desc, $setup, $code) = @_;
-
+    my ($name, $test, $body) = @_;
+    my ($desc, $setup, $code, $pre, $post) =
+                                    @$test{qw(desc setup code pre post)};
+
+    $pre  = defined $pre  ? "_PRE_: $pre; " : "";
+    $post = defined $post ? "_POST_: $post; " : "";
+    $code = $body ? $code : "1";
+    $code = "_CODE_: $code; ";
     return <<EOF;
 # $desc
-package $test;
+package $name;
 BEGIN { srand(0) }
 $setup;
 for my \$__loop__ (1..\$ARGV[0]) {
-    $code;
+    $pre$code$post
 }
 EOF
 }
@@ -1143,14 +1157,9 @@ sub grind_run {
     for my $test (grep $tests->{$_}, @$order) {
 
         # Create two test progs: one with an empty loop and one with code.
-        # Note that the empty loop is actually '{1;}' rather than '{}';
-        # this causes the loop to have a single nextstate rather than a
-        # stub op, so more closely matches the active loop; e.g.:
-        #   {1;}    => nextstate;                       unstack
-        #   {$x=1;} => nextstate; const; gvsv; sassign; unstack
         my @prog = (
-            make_perl_prog($test, @{$tests->{$test}}{qw(desc setup)}, '1'),
-            make_perl_prog($test, @{$tests->{$test}}{qw(desc setup code)}),
+            make_perl_prog($test, $tests->{$test}, 0),
+            make_perl_prog($test, $tests->{$test}, 1),
         );
 
         for my $p (@$perls) {
index d681812..f366d4b 100644 (file)
 #     string::   string handling
 #
 #
-# Each hash has three fields:
+# Each hash has up to five fields:
 #
 #   desc  is a description of the test; if not present, it defaults
 #           to the same value as the 'code' field
 #
-#   setup is a string containing setup code
+#   setup is an optional string containing setup code that is run once
 #
 #   code  is a string containing the code to run in a loop
 #
-# So typically a benchmark tool might do something like
+#   pre   is an optional string containing setup code which is executed
+#         just before 'code' for every iteration, but whose execution
+#         time is not included in the result
 #
-#   eval "package $token; $setup; for (1..1000000) { $code }"
+#   post  like pre, but executed just after 'code'.
+#
+# So typically a benchmark tool might execute variations on something like
+#
+#   eval "package $name; $setup; for (1..1000000) { $pre; $code; $post }"
 #
 # Currently the only tool that uses this file is Porting/bench.pl;
 # try C<perl Porting/bench.pl --help> for more info
@@ -60,7 +66,9 @@
 # Note: for the cachegrind variant, an entry like
 #    'foo::bar' => {
 #     setup   => 'SETUP',
+#     pre     => 'PRE',
 #     code    => 'CODE',
+#     post    => 'POST',
 #   }
 # creates two temporary perl sources looking like:
 #
 #        BEGIN { srand(0) }
 #        SETUP;
 #        for my $__loop__ (1..$ARGV[0]) {
-#            1;
+#            PRE; 1; POST;
 #        }
 #
-# and as above, but with the '1;' in the loop  body replaced with:
+# and as above, but with the loop body replaced with:
 #
-#            CODE;
+#            PRE; CODE; POST;
 #
 # It then pipes each of the two sources into
 #
 # where N is set to 10 and then 20.
 #
 # It then uses the result of those four cachegrind runs to subtract out
-# the perl startup and loop overheads. So only what's in SETUP and CODE
-# can affect the benchmark, and if the loop happens to leave some state
-# changed (such as storing a value in a hash), then the final benchmark
-# timing is the result of running CODE with the hash entry populated
-# rather than empty.
+# the perl startup and loop overheads (including SETUP, PRE and POST), leaving
+# (in theory only CODE);
+#
+# Note that misleading results may be obtained if each iteration is
+# not identical. For example with
+#
+#     code => '$x .= "foo"',
+#
+# the string $x gets longer on each iteration. Similarly, a hash might be
+# empty on the first iteration, but have entries on subsequent iterations.
+#
+# To avoid this, use 'pre' or 'post', e.g.
+#
+#     pre  => '$x  = ""',
+#     code => '$x .= "foo"',
+#
 
 
 [