Beefy Boxes and Bandwidth Generously Provided by pair Networks
The stupid question is the question not asked

Re^6: "exists $hash{key}" is slower than "$hash{key}"

by swl (Priest)
on Jan 07, 2020 at 03:15 UTC ( #11111101=note: print w/replies, xml ) Need Help??

in reply to Re^5: "exists $hash{key}" is slower than "$hash{key}"
in thread "exists $hash{key}" is slower than "$hash{key}"

Yes, it is most definitely a micro-optimisation. This is consistent with the last two paras in my original post, although I did not explicitly use that term there.

  • Comment on Re^6: "exists $hash{key}" is slower than "$hash{key}"

Replies are listed 'Best First'.
Re^7: "exists $hash{key}" is slower than "$hash{key}"
by dave_the_m (Monsignor) on Jan 07, 2020 at 11:08 UTC
    Most of the above thread is, err, basically wrong! The two Concise outputs show that the only two ops executed in each case are the GV (to get the glob containing the global hash variable) followed by a MULTIDEREF op. The ex-helem and ex-exists are ops that were optimised away and replaced with the MULTIDEREF op (on perl 5.22.0 and later, anyway) - they are no longer on the execution path.

    The difference in performance between exists and a plain hash lookup is negligible, although exists should be slightly faster. The slowdown being seen is due to assigning a boolean return value to a scalar. Perl's boolean values (as returned by exists etc) are multi-valued scalars containing an int value of 0/1, a floating value of 0.0/1.0 and a string value of ""/"1". If this value is assigned to a scalar, all three parts need to be copied, including the string. Boolean values are optimised for use in conditionals. If you replace the assignment with something like $xx_global = exists $hash{$key2} ? 1 : 2 you'll see exists becomes faster than a hash lookup.


      so the ->#NUMBER at the end of the lines are GOTOs and the - labels indicate ignored op-codes?

      2 <;> nextstate(main 74 -e:1) v:%,{,469764096 ->3 - <1> ex-exists vK/1 ->4 3 <+> multideref($h{"a"}) vK/EXISTS ->4

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

        Effectively yes. The ->#N is the pointer to the next op to be executed (op_next field), which is often not in the same order as the optree structure. Op nodes displayed as "ex-FOO" are ops that are no longer needed and have been converted into OP_NULL (with any attached data freed), but with their former type still recorded (mainly as a debugging aid). These OP_NULLs are usually removed from the execution path.

        Note that -MO=Concise,-exec shows ops in execution order, which often makes things clearer.


      Thanks for the comments and clarifications.

      If I update the benchmark code to use the ternary operator, and only a global assignment, then the general pattern remains on windows, but less so on linux.

      Is anyone able to replicate these results?

      I ran the above code on both a linux box (perlbrew 5.30.0, CentOS 7) and a windows laptop (windows 10, Strawberry perl 5.30.0). Each was repeated four times. (In previous posts I used Strawberry 5.28.0, but the windows machine is the same).

      The relative differences on the linux machine are very small and the order changes between runs. One of the value calls is fastest across each of the runs, but not by much in absolute terms, and the exists call is second fastest for three of the four calls. On windows the value calls are always faster than the exists calls and the relative differences are much greater.

      Linux results:


      Windows results:

      And I should reiterate my point from the original post that the relative differences remain very small. If the difference is real, then one would have to be running a very large number of calls for the choice of idiom to make any meaningful difference.


      After writing the above, I decided to run more replications on Windows to get a better sense of how consistent the results are on my machine, and get the results below for 30 replications. I could have simplified the benchmarks to one of each type, but have left the code as-is for simplicity.

      Of the 30 reps, 23 show both value calls being faster than either exists call. In only one case was exists fastest.

        At this point I think you're mainly measuring noise. You've also still got the bug whereby you populate the lexical %hash, but the benchmarks get run against the *empty* global %hash.

        By "noise", I mean a combination of timing noise, and (for lack of a better term) "compiler noise". How C code gets compiled can effect the alignment of machine code bytes across cache line boundaries, which means that different compilers can compile the same source code of the perl interpreter into different executables which have different instruction cache and branch prediction miss patterns. I have personally seen adding a line of code into a part of the perl interpreter which wasn't being executed (e.g. in dump.c) cause a 10% change in benchmark speed for a simple benchmark.

        These days I mostly benchmark the perl interpreter using a tool of mine (Porting/ based on top of cachegrind, which profiles the execution run in terms of how many individual machine code instructions, branches etc it does. Under that, 'exists' takes slightly fewer instruction and data reads and writes and branches than a hash lookup.


        is it noise?

        sub BenchIt { print "\n\n## $^O $]\n"; use Benchmark qw {:all}; our %hash; for (1001..2000) { $hash{$_}++; } our $key1 = 2000 - int rand 1001; our $key2 = 2000 - int rand 1001; $hash{$key1} = 1; $hash{$key2} = {1..10}; our $xx_global; cmpthese ( -2, { svExist => 'for(1..10_000){$xx_global = exists $hash{$ke +y1} ? 1 : 2}', svValue => 'for(1..10_000){$xx_global = $hash{$key1} ? 1 + : 2}', refExist => 'for(1..10_000){$xx_global = exists $hash{$ke +y2} ? 1 : 2} ', refValue => 'for(1..10_000){$xx_global = $hash{$key2} ? 1 + : 2}', } ); return; }

        a few old perls

        laptops fluctuate :)

Log In?

What's my password?
Create A New User
Node Status?
node history
Node Type: note [id://11111101]
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others scrutinizing the Monastery: (7)
As of 2020-09-28 06:43 GMT
Find Nodes?
    Voting Booth?
    If at first I donít succeed, I Ö

    Results (143 votes). Check out past polls.