<?xml version="1.0" encoding="windows-1252"?>
<node id="587072" title="When should I use a dispatch table?" created="2006-11-30 18:00:09" updated="2006-11-30 13:00:09">
<type id="120">
perlmeditation</type>
<author id="180961">
Limbic~Region</author>
<data>
<field name="doctext">
All,
&lt;br /&gt;
A typical dispatch table is a technique used to translate:
&lt;CODE&gt;
if    ($item eq 'thing1') { # ... }
elsif ($item eq 'thing2') { # ... }
elsif ($item eq 'thing3') { # ... }
elsif ($item eq 'thing4') { # ... }
else                      { # ... }
&lt;/CODE&gt;
Into: &lt;code&gt;exists $dispatch{$item} ? $dispatch{$item}-&gt;() : $dispatch{default}-&gt;();&lt;/code&gt;
&lt;p&gt;
In addition to opportunities for code reduction and clean maintainable code, dispatch tables can be a runtime optimization.  The if/eslif chain is a linear scan O(N) while a hash is purported to be O(1).  The hash does have the overhead of looking up the key, dereferencing the value, and executing the code ref.  I assumed that in what only take a few equality checks to pay for this overhead.
&lt;/p&gt;
&lt;p&gt;
In [id://586684|this node], I claimed that my version of the code was much more efficient.  That node inspired [madbombX] to convert an if/eslif chain into a dispatch table and benchmark it.  Unfortunately, the bench was flawed and the results skewed.  This lead me to write code to generate a fair benchmark based off user input.
&lt;/p&gt;
&lt;readmore&gt;
&lt;code&gt;
#!/usr/bin/perl
use strict;
use warnings;
use Getopt::Std;
use HTML::Template;
use List::Util;

my %opt;
get_args(\%opt);

my $template = HTML::Template-&gt;new(filename =&gt; 'bench.tmpl');
$template-&gt;param(CHAIN_LENGTH    =&gt; $opt{n});
$template-&gt;param(UNMATCHED_ITEMS =&gt; $opt{u});

my @chain = map {ITEM =&gt; $_}, 2 .. $opt{n};
$template-&gt;param(CHAIN =&gt; \@chain);

my $unmatched_items = int(($opt{u} / 100) * $opt{t});
$unmatched_items  ||= 1 if $opt{u};

my @input  = (-1) x $unmatched_items;
my $total  = $opt{t} - $unmatched_items;
my @weight = $opt{w}
    ? sort {$b &lt;=&gt; $a} grep $_, split /[%,]|\s+/, $opt{w}
    : (int(100/$opt{n})) x $opt{n};

for (1 .. $opt{n}) {
    my $amount = shift @weight;
    push @input, ($_) x (int(($amount/100) * $total) || 1);
}

$_ = {FIND =&gt; $_} for @input;
@input = List::Util::shuffle(@input) if ! $opt{o};
$template-&gt;param(BUILD_FIND =&gt; \@input);
print $template-&gt;output();

sub get_args {
    my $opt = shift @_;
    my $Usage = qq{Usage: $0 [options]
        -h : This help message

        -n : The (n)umber of items in the if/elsif chain
             Default: 5

        -t : The (t)total number of items to process
             This number need not be the same as your actual data.
             It should be sufficient to exercise the options
             Default: -n * 10

        -u : The percentage of (u)nmatched items to include
             Typical if/elsif chains end with a catch-all else
             This option allows you to determine how often this is used
             Default: 0

        -o : Specifies if the if/elsif chain should be (o)rdered
             If the input data is well known, it is possible to order
             the if/elsif chains to ensure the most common items are first
             Default: 0 (off)

        -w : The (w)eight in percentages of the matched input
             Ignored unless -o option also specified
             There must be as many #% as there are -n to work correctly
             The #% must add up to 100 to work correctly
             The -b option, if present, takes precedence before this option
             For instance, if you have n = 5, t = 100, -o, and u = 10
             -w"40%, 30%, 15%, 10%, 5%" would equate to 100% of the remaining 90%
             Default: N/A
    } . "\n";
    getopts('hn:t:u:w:o', $opt) or die $Usage;
    die $Usage                 if $opt-&gt;{h};
    delete $opt-&gt;{w}           if ! exists  $opt-&gt;{o};
    $opt-&gt;{n} = 5              if ! defined $opt-&gt;{n};
    $opt-&gt;{t} = $opt-&gt;{n} * 10 if ! defined $opt-&gt;{t};
    $opt-&gt;{u} = 0              if ! defined $opt-&gt;{u};
}
&lt;/code&gt;
bench.tmpl
&lt;code&gt;
#!/usr/bin/perl
use strict;
use warnings;
use Benchmark 'cmpthese';

my %dispatch = map {$_ =&gt; sub {my $res = "I found it"}} 1 .. &lt;TMPL_VAR NAME="CHAIN_LENGTH"&gt;;
&lt;TMPL_IF NAME="UNMATCHED_ITEMS"&gt;
$dispatch{default} = sub {my $res = "I did not find it"};
&lt;/TMPL_IF&gt;

my @find;
&lt;TMPL_LOOP NAME=BUILD_FIND&gt;
push @find, &lt;TMPL_VAR NAME=FIND&gt;;
&lt;/TMPL_LOOP&gt;

cmpthese(-10, {
    dispatch =&gt; sub {
        for (@find) {
            &lt;TMPL_IF NAME="UNMATCHED_ITEMS"&gt;
            if (exists $dispatch{$_}) {
                $dispatch{$_}-&gt;();
            }
            else {
                $dispatch{default}-&gt;();
            }
            &lt;TMPL_ELSE&gt;
            $dispatch{$_}-&gt;();
            &lt;/TMPL_IF&gt;
        }
    },
    if_else =&gt; sub {
        for (@find) {
            if ($_ eq '1') {
                my $res = "I found it";
            }
            &lt;TMPL_LOOP NAME=CHAIN&gt;
            elsif($_ eq '&lt;TMPL_VAR NAME=ITEM&gt;') {
                my $res = "I found it";
            }
            &lt;/TMPL_LOOP&gt;
            &lt;TMPL_IF NAME="UNMATCHED_ITEMS"&gt;
            else {
                my $res = "I did not find it";
            }
            &lt;/TMPL_IF&gt;
        }
    },
});
&lt;/code&gt;
&lt;/readmore&gt;
&lt;p&gt;
The results were quite amazing.  No matter how I tweaked the settings, with only 6 items in the if/elsif chain I could not make the dispatch table win.  It is still possible that the other optimizations I made to the code in that node made it more efficient but without a representative sample of the real input - I can't be sure.
&lt;/p&gt;
&lt;p&gt;
The breakpoint without unmatched items is about 12 on my system.  There are plenty of user defineable variables so perhaps you might want to &lt;del&gt;question code you have that uses dispatch tables&lt;/del&gt; &lt;ins&gt;&lt;i&gt;play around&lt;/i&gt;&lt;/ins&gt;.  While the code reduction and maintainability is still an advantage, the performance boost may not be all it is cracked up to be. 
&lt;/p&gt;
&lt;p&gt;
&lt;b&gt;Update:&lt;/b&gt; Minor change to wording in last paragraph after realizing it may imply I was recommending not using dispatch tables because they were not as efficient as I assumed them to be originally.
&lt;/p&gt;
&lt;p&gt;
&lt;small&gt;
&lt;b&gt;See Also:&lt;/b&gt;
&lt;ul&gt;
&lt;li&gt;[id://456530]&lt;/li&gt;
&lt;li&gt;[id://573138]&lt;/li&gt;
&lt;/ul&gt;
&lt;/small&gt;
&lt;/p&gt;
&lt;div class="pmsig"&gt;&lt;div class="pmsig-180961"&gt;
&lt;p&gt;
Cheers - [Limbic~Region|L~R]
&lt;/p&gt;
&lt;/div&gt;&lt;/div&gt;</field>
</data>
</node>
