Feb 16, 2014

Benchmarked Sort::Maker's each sort style for my specific purpose

I use various internal and external APIs to build my services. An external API returns content something like below:
{
    "status": 1,
    "error_msg": '',
    "result": {
        "foo1": { .... },
        "foo2": { .... },
        "foo3": { .... },
        "foo4": { .... }
    }
}
After retrieving this, I have to sort the result in result.foo1, result.foo2, ..., result.foo100 order. I'm wondering why they don't return this in array, but I must face it as long as the provider is returning values in this way. Although Perl Best Practices insists on using Sort::Maker, I implemented this in my way because this sort wasn't that complicated.
my %tmp_cache; # for orcish maneuver
my @sorted_keys = sort {
    ( $tmp_cache{$a} //= $a =~ s/\A foo(\d+) \z/$1/xr ) <=>
    ( $tmp_cache{$b} //= $b =~ s/\A foo(\d+) \z/$1/xr )
} keys %{ $content->{result} };

# then sorted results are stored in stash to be displayed
$c->stash->{results} = [ map { $content->{result}->{$_} } @sorted_keys ];
Today, I benchmarked each sort type provided by Sort::Maker and found that using Sort::Maker was much faster. The code is as below. It was a bit surprising that my original method with orcish maneuver is13% slower than Sort::Maker's one with orcish maneuver. Needless to say, other sort types are much faster. So my conclusion is that even relatively simple sort should be implemented with Sort::Maker to increase readability, maintenancibility and performance.
#!/usr/bin/env perl
use strict;
use warnings;
use Sort::Maker qw/make_sorter/;
use Benchmark qw/:all/;
warn "Perl: $]\n";
warn "Sort::Maker: $Sort::Maker::VERSION\n\n";
#Perl: 5.018001
#Sort::Maker: 0.06
#
# Rate plain original orcish ST GRT
#plain 6.83/s -- -79% -82% -85% -86%
#original 33.2/s 386% -- -13% -25% -34%
#orcish 38.4/s 462% 16% -- -14% -23%
#ST 44.5/s 551% 34% 16% -- -11%
#GRT 50.2/s 635% 51% 31% 13% --
my $hashref = +{
status => 1,
error_msg => '',
result => +{},
};
$hashref->{result}->{ sprintf('foo%d', $_) } = rand(100) for 1..5_000;
my $sort_rule = +{
number => +{
code => sub { /\A foo(\d+) \z/xms },
ascending => 1,
}
};
my $sorter_plain = make_sorter( plain => 1, %$sort_rule );
my $sorter_orc = make_sorter( orcish => 1, %$sort_rule );
my $sorter_st = make_sorter( ST => 1, %$sort_rule );
my $sorter_grt = make_sorter( GRT => 1, %$sort_rule );
my $sorter_orig = sub {
my %tmp_cache;
sort {
( $tmp_cache{$a} //= $a =~ s/\A foo(\d+) \z/$1/xr ) <=>
( $tmp_cache{$b} //= $b =~ s/\A foo(\d+) \z/$1/xr )
} @_;
};
cmpthese(
1_000,
+{
plain => sub {
my @sorted_keys = $sorter_plain->( keys %{ $hashref->{result} } );
},
orcish => sub {
my @sorted_keys = $sorter_orc->( keys %{ $hashref->{result} } );
},
ST => sub {
my @sorted_keys = $sorter_st->( keys %{ $hashref->{result} } );
},
GRT => sub {
my @sorted_keys = $sorter_grt->( keys %{ $hashref->{result} } );
},
original => sub {
my @sorted_keys = $sorter_orig->( keys %{ $hashref->{result} } );
},
},
);
__END__