Anonymous Monk has asked for the wisdom of the Perl Monks concerning the following question:

#!/usr/bin/perl use strict; use Benchmark qw(cmpthese); sub a { my @list = @{shift()}; $_ +=1 foreach @list; return \@list; }; sub b { my @list = @{shift()}; return [map {$_ += 1} @list]; }; my @foo = 1..10; cmpthese (10, { a => \&a(\@foo), b => \&b(\@foo), });
I contructed this minimal snippet from my program but cannot get it to run. I already tried

I receive various syntax errors, either from Benchmark or Perl itself, or Can't use string ("10") as an ARRAY ref while "strict refs" in use.

I don't understand the output of diagnostics. Explain what's wrong.

Replies are listed 'Best First'.
Re: cannot get Benchmark to work
by hv (Prior) on Jul 04, 2005 at 13:16 UTC

    You can't write \&a(\@foo) - you can take a reference to a subroutine like \&a, but then you can't specify parameters.

    One solution is to create an anonymous subroutine that does the call:

    a => sub { a(\@foo) }, b => sub { b(\@foo) },

    Another approach (usually recommended with Benchark) is to provide a string to eval rather than a subroutine reference:

    a => q{ a(\@foo) }, b => q{ b(\@foo) },
    .. but in that case you need to be careful with variables: your my() variables won't be visible to Benchmark when it evals these, so you'd need to declare @foo in this case as a package variable:
    our @foo = 1..10;

    Hugo

      Another approach (usually recommended with Benchark) is to provide a string to eval rather than a subroutine reference
      (Bold is mine). I dropped into the Benchmark documentation, and I indeed found:
      a code reference will show a slightly slower execution time than the equivalent eval'd string
      I only wonder... why?

      Flavio
      perl -ple'$_=reverse' <<<ti.xittelop@oivalf

      Don't fool yourself.

        It is because Benchmark evals the code either way. It runs the code in the calling package, so if you give it a string to eval, then it just concatenates the package switching wrapper around the code, and evals it. If you give it a code ref, it just wraps the string "&$ref()" in a similar fashion.

        Either way, you suffer the same eval STR overhead. In the subref case, you also suffer the cost of a function call.

        a code reference will show a slightly slower execution time than the equivalent eval'd string
        I only wonder... why?
        Because if you pass it a code reference, your benchmark needs to call a subroutine on each iteration. If you run your benchmark 10000 times, you will be calling 10000 subroutines, that is, you create 10000 blocks, and break them down 10000 times (think ref counting overhead), all which have nothing to do with the thing you are benchmarking.
Re: cannot get Benchmark to work
by fishbot_v2 (Chaplain) on Jul 04, 2005 at 13:19 UTC

    Try:

    cmpthese (10, { a => sub { a( \@foo ) }, b => sub { b( \@foo ) }, });

    You are trying to take a reference to a sub with arguments. That doesn't work. Try Data::Dumpering the result of \&a(\@foo). It is a reference to the arrayref that a() returns... ie. perl calls the sub, then creates a ref to the result, which in turn cmpthese() can't do much with.

      I would not do that. It hides what you want to benchmark not behind one, but two subroutine calls. Which are expensive in Perl. Much better is it to use strings, as shown in the benchmark below. It shows that the foreach statement modifier is faster than the map BLOCK variant (which isn't at all surprising as the modifier doesn't need to create a scope - on addition to that, the map BLOCK variant makes an additional copy of the list), but while the double sub shows a 63% speed difference, the string version shows a 92% difference.
      #!/usr/bin/perl use strict; use warnings; use Benchmark qw(cmpthese); our @foo = 1 .. 10; sub a { my @list = @{shift()}; $_ += 1 foreach @list; return \@list; } sub b { my @list = @{shift()}; return [map {$_ += 1} @list]; } cmpthese(500_000, { a => sub {a(\@foo)}, b => sub {b(\@foo)}, c => 'my @list = @foo; $_ += 1 foreach @list; \@list', d => 'my @list = @foo; [map {$_ += 1} @list]', }); __END__ Rate b d a c b 68587/s -- -9% -39% -52% d 74963/s 9% -- -33% -48% a 111607/s 63% 49% -- -22% c 143678/s 109% 92% 29% --
      Note that the fastest solution is map EXPR in void context (well, at least on my computer), as shown by the benchmark below:
      #!/usr/bin/perl use strict; use warnings; use Benchmark qw(cmpthese); our @foo = 1 .. 10; sub a { my @list = @{shift()}; $_ += 1 foreach @list; return \@list; } sub b { my @list = @{shift()}; return [map {$_ += 1} @list]; } sub c { my @list = @{shift()}; map {$_ += 1} @list; return \@list; } sub d { my @list = @{shift()}; map $_ += 1, @list; return \@list; } cmpthese(250_000, { a1 => sub {a(\@foo)}, b1 => sub {b(\@foo)}, c1 => sub {c(\@foo)}, d1 => sub {d(\@foo)}, a2 => 'my @list = @foo; $_ += 1 foreach @list; \@list', b2 => 'my @list = @foo; [map {$_ += 1} @list]', c2 => 'my @list = @foo; map {$_ += 1} @list; \@list', d2 => 'my @list = @foo; map $_ += 1, @list; \@list', }); __END__ Rate b1 b2 c1 c2 a1 d1 a2 d2 b1 68493/s -- -9% -11% -24% -38% -44% -51% -57% b2 75529/s 10% -- -2% -16% -32% -38% -46% -53% c1 76687/s 12% 2% -- -14% -31% -37% -45% -52% c2 89606/s 31% 19% 17% -- -19% -27% -36% -44% a1 110619/s 62% 46% 44% 23% -- -9% -21% -31% d1 121951/s 78% 61% 59% 36% 10% -- -13% -24% a2 140449/s 105% 86% 83% 57% 27% 15% -- -12% d2 160256/s 134% 112% 109% 79% 45% 31% 14% --