http://www.perlmonks.org?node_id=1231220

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

Hi

the following code was intended to show to my colleagues that hash-slices are faster than maps.

What's surprising me is that a variant with explicit keys is always faster than using a constant list.

And the latter is slower than using a variable array!!!

Please note that B::Deparse doesn't show any code difference (haven't run it through B::Concise yet)

What am I doing wrong?

-*- mode: compilation; default-directory: "d:/Users/LanX/vm_share/pm/" + -*- Compilation started at Wed Mar 13 13:03:10 C:/Perl_524/bin\perl.exe d:/Users/LanX/vm_share/pm/benchmark_slice.pl + Smartmatch is experimental at d:/Users/LanX/vm_share/pm/benchmark_slic +e.pl line 60. hash %h: { xxxxa => 12345, xxxxb => 12346, xxxxc => 12347, xxxxd => 12348, xxxxe => 12349, xxxxf => 12350, } Rate map_const map_var slice_const slice_var slic +e_explicit map_const 326753/s -- -36% -86% -87% + -88% map_var 511706/s 57% -- -78% -79% + -82% slice_const 2284993/s 599% 347% -- -6% + -18% slice_var 2423666/s 642% 374% 6% -- + -14% slice_explicit 2802527/s 758% 448% 23% 16% + -- ok 1 - slice_const { use warnings; use strict; no feature ':all'; use feature ':5.12'; join ',', @h{'xxxxa', 'xxxxb', 'xxxxc', 'xxxxd', 'xxxxe', 'xxxxf'} +; } ok 2 - map_const ok 3 - map_var ok 4 - slice_var ok 5 - slice_explicit { use warnings; use strict; no feature ':all'; use feature ':5.12'; join ',', @h{'xxxxa', 'xxxxb', 'xxxxc', 'xxxxd', 'xxxxe', 'xxxxf'} +; } 1..5 Compilation finished at Wed Mar 13 13:03:55

use 5.012; use strict; use warnings; use Data::Dump qw/pp dd/; use Benchmark qw( cmpthese ) ; use Test::More; use B::Deparse; my $deparse = B::Deparse->new(); use constant SHOW_DEPARSE => 1; my (@keys, @values, %h, $expected); BEGIN { my $n_keys = 6; my ( $first_key, $first_value ) = ("xxxxa",12345); push @keys, $first_key++ for 1.. $n_keys; push @values, $first_value++ for 1.. $n_keys; @h{@keys} = @values; # init hash $expected = join ",", @values; eval <<"__CODE__"; sub slice_explicit { join ",", \@h{ qw/@keys/} } __CODE__ } warn "hash %h: ", pp(\%h) ,"\n"; use constant LIST=>(@keys); my @list= @keys; my $h_subs = { map_var => sub{ join ",", map { $h{$_} } @list }, map_const => sub{ join ",", map { $h{$_} } LIST }, slice_var => sub{ join ",", @h{@list} }, slice_const => sub{ join ",", @h{LIST()} }, slice_explicit => \&slice_explicit, } ; cmpthese( -5, $h_subs ); # --- test validity while ( my ($name,$sub) = each %$h_subs ) { my $result = $sub->(); is($result, $expected, $name); # --- show code say $deparse->coderef2text($sub) if SHOW_DEPARSE and $name ~~ [qw/slice_const slice_explicit/]; } done_testing();

Cheers Rolf
(addicted to the Perl Programming Language :)
Wikisyntax for the Monastery FootballPerl is like chess, only without the dice

Replies are listed 'Best First'.
Re: Benchmark: Constant List in Hash Slice
by dave_the_m (Monsignor) on Mar 13, 2019 at 14:10 UTC
    Literal keys in hash subscripts and hash slices have the the hash (as in checksum) values of their strings pre-computed, and the string part of the string constant is stored in such a way that its easy and quick to pass directly to the hash lookup API functions.

    Technically: at compile time, the PV part of the const SV is converted to a HEK, with SvPV(sv) pointing to the string part within the HEK. So it looks like a string but is also a HEK.

    Dave.

      OK I thought the inlining of the constant list might happen early enough to trigger this pre-computation.

      But the optimizer doesn't see the inlined keys as literal strings, so it has to calculate this lookup-index dynamically at run-time.

      Cheers Rolf
      (addicted to the Perl Programming Language :)
      Wikisyntax for the Monastery FootballPerl is like chess, only without the dice

      does this also explain why using a normal @array variable is faster than a constant list?

      Cheers Rolf
      (addicted to the Perl Programming Language :)
      Wikisyntax for the Monastery FootballPerl is like chess, only without the dice

        does this also explain why using a normal @array variable is faster than a constant list?
        No. That's because constant arrays are implemented as constant array refs. So
        use constant foo qw(a b c); @a = $h{foo()}
        is implemented roughly as
        use constant foo [qw(a b c)]; @a = $h{@{foo()}}
        And neither (as far as I'm aware) get the compile-time HEK treatment

        Dave.

Re: Benchmark: Constant List in Hash Slice
by tobyink (Canon) on Mar 13, 2019 at 14:18 UTC

    Although use constant LIST=>(@keys) defines a list constant, Perl doesn't currently optimize this like it does with scalar constants. So every time you use the constant, it performs a sub call. That's why it's slower.

    Update: it seems my knowledge was out of date. I've just checked and it turns out they were optimized in Perl 5.20. I'm guessing they weren't optimized quite as well as scalar constants though.

Re: Benchmark: Constant List in Hash Slice
by hdb (Monsignor) on Mar 13, 2019 at 12:29 UTC

    Your test, retrieving the full hash as a slice, looks strange to me. I think it is more meaningful to retrieve a few elements from a large hash, say 1% or 0.1% of the keys.

      The requirement in this case is to sort according to an array not cutting out a subset.

      Update

      And yes I know that using an array combined with enum constant indices are faster, but my concern here is about the strange side effect from inlining a constant list.

      Cheers Rolf
      (addicted to the Perl Programming Language :)
      Wikisyntax for the Monastery FootballPerl is like chess, only without the dice