perlmeditation
shotgunefx
I often use very deeply nested structures in my code. This can make for ugly code. <i>$self->{thiskey}{theykey}{etc}="etc..."</i><br>
I've been going back and cleaning up some older code by taking a ref to the HoHoHoHo..s and it got me to thinking about hashes in general( That and reading perlguts).
<br><br>I always assumed that when you said $hash{'constant_key'}->{key2}, Perl would optimize this on compile and not keep fetching $hash{'constant_key'} after it first encountered it.
<br><br>
I thought wrong. <readmore>It would appear that using a reference to the desired "Hash in a Hash" has a fairly large performance implication. This very well may be a "Duh!" for others but for me this was very eye opening.
Using a reference to a HoH speeds things up on average about 15%! A more practical test (HoH) bears this out. Not a bad speedup.
On HoHoH it averages about 35% faster. The deeper the reference, the bigger the payoff.
<br><br>
I don't do a lot of benchmarking so if someone sees a flaw in my method, I'd appreciate feedback.
<br><br>
<b>Lesson: So not only does using refs to nested data structures make your code easier to read, it makes it a good bit faster.</b>
<code>
#!/usr/bin/perl
use strict;
use warnings;
use Benchmark qw(cmpthese timethese);
my $char = 'a';
my %hash = map {$char++.$_ => $_} (1..50);
my %HoH = map {$char++.$_ => {%hash} } (1..20);
my %HoHoH = map {$char++.$_ => {%HoH} } (1..50);
my ($ref,$ref1,$ref2,$ref3,$ref4);
$ref1->{key1} = {%hash};
$ref2->{key1}{key2} = {%hash};
$ref3->{key1}{key2}{key3} = {%hash};
$ref4->{key1}{key2}{key3}{key4} = {%hash};
my ($shortref,$shortref1,$shortref2,$shortref3,$shortref4) = (
\%hash,
$ref1->{key1},
$ref2->{key1}{key2},
$ref3->{key1}{key2}{key3},
$ref4->{key1}{key2}{key3}{key4},
);
my $iterations = 10000;
my @benchmarks = (
{count=> $iterations, hash=>sub { foreach my $k ( keys %hash ) { $hash{$k}++ } },
ref=>sub { foreach my $k ( keys %{$shortref} ) { $shortref->{$k}++ } } },
{count=> $iterations, ref2hash1=>sub { foreach my $k ( keys %{$ref1->{key1}}) { $ref1->{key1}{$k}++ }},
shortref1=>sub { foreach my $k ( keys %{$shortref1}) { $shortref1->{$k}++ }} },
{count=> $iterations, ref2hash2=>sub { foreach my $k ( keys %{$ref2->{key1}{key2}}) { $ref2->{key1}{key2}{$k}++ }},
shortref2=>sub { foreach my $k ( keys %{$shortref2}) { $shortref2->{$k}++ }} },
{count=> $iterations, ref2hash3=>sub { foreach my $k ( keys %{$ref3->{key1}{key2}{key3}}) { $ref3->{key1}{key2}{key3}{$k}++ }},
shortref3=>sub { foreach my $k ( keys %{$shortref3}) { $shortref3->{$k}++ }} },
{count=> $iterations, ref2hash4=>sub { foreach my $k ( keys %{$ref4->{key1}{key2}{key3}{key4}}) { $ref4->{key1}{key2}{key3}{key4}{$k}++ }},
shortref4=>sub { foreach my $k ( keys %{$shortref4}) { $shortref4->{$k}++ }} },
{
count=>2000,
HoH =>sub {
foreach my $k (keys %HoH) {
foreach (keys %{ $HoH{$k} } ){
$HoH{$k}->{$_}++;
}
}
},
refHoH =>sub {
foreach my $k (keys %HoH) {
my $hashref = $HoH{$k};
foreach (keys %{ $hashref } ){
$hashref->{$_}++;
}
}
},
},
{
count=>100,
HoHoH =>sub { # Traverse
foreach my $k (keys %HoHoH) {
foreach my $k2 (keys %{ $HoHoH{$k} } ){
foreach (keys %{ $HoHoH{$k}->{$k2} } ){
$HoHoH{$k}{$k2}->{$_}++;
}
}
}
},
refHoHoH =>sub { # Traverse
foreach my $k (keys %HoHoH) {
my $hashref = $HoHoH{$k};
foreach (keys %{ $hashref } ){
my $hashref2 = $hashref->{$_};
foreach (keys %{$hashref2} ){
$hashref2->{$_}++;
}
}
}
},
},
);
foreach my $bench (@benchmarks){
my $results = timethese(delete $bench->{count} ,$bench);
print "-" x 20,"\n";
cmpthese($results);
print "-" x 40,"\n\n";
}
__END__
Benchmark: timing 10000 iterations of hash, ref...
hash: 2 wallclock secs ( 2.06 usr + 0.00 sys = 2.06 CPU) @ 4854.37/s (n=10000)
ref: 2 wallclock secs ( 2.18 usr + 0.00 sys = 2.18 CPU) @ 4587.16/s (n=10000)
--------------------
Rate ref hash
ref 4587/s -- -6%
hash 4854/s 6% --
----------------------------------------
Benchmark: timing 10000 iterations of ref2hash1, shortref1...
ref2hash1: 3 wallclock secs ( 2.47 usr + 0.00 sys = 2.47 CPU) @ 4048.58/s (n=10000)
shortref1: 3 wallclock secs ( 2.20 usr + 0.00 sys = 2.20 CPU) @ 4545.45/s (n=10000)
--------------------
Rate ref2hash1 shortref1
ref2hash1 4049/s -- -11%
shortref1 4545/s 12% --
----------------------------------------
Benchmark: timing 10000 iterations of ref2hash2, shortref2...
ref2hash2: 3 wallclock secs ( 2.90 usr + 0.00 sys = 2.90 CPU) @ 3448.28/s (n=10000)
shortref2: 2 wallclock secs ( 2.14 usr + 0.00 sys = 2.14 CPU) @ 4672.90/s (n=10000)
--------------------
Rate ref2hash2 shortref2
ref2hash2 3448/s -- -26%
shortref2 4673/s 36% --
----------------------------------------
Benchmark: timing 10000 iterations of ref2hash3, shortref3...
ref2hash3: 3 wallclock secs ( 3.35 usr + 0.00 sys = 3.35 CPU) @ 2985.07/s (n=10000)
shortref3: 3 wallclock secs ( 2.23 usr + 0.00 sys = 2.23 CPU) @ 4484.30/s (n=10000)
--------------------
Rate ref2hash3 shortref3
ref2hash3 2985/s -- -33%
shortref3 4484/s 50% --
----------------------------------------
Benchmark: timing 10000 iterations of ref2hash4, shortref4...
ref2hash4: 4 wallclock secs ( 3.58 usr + 0.00 sys = 3.58 CPU) @ 2793.30/s (n=10000)
shortref4: 2 wallclock secs ( 2.16 usr + 0.00 sys = 2.16 CPU) @ 4629.63/s (n=10000)
--------------------
Rate ref2hash4 shortref4
ref2hash4 2793/s -- -40%
shortref4 4630/s 66% --
----------------------------------------
Benchmark: timing 2000 iterations of HoH, refHoH...
HoH: 16 wallclock secs (10.01 usr + 0.00 sys = 10.01 CPU) @ 199.80/s (n=2000)
refHoH: 9 wallclock secs ( 8.98 usr + 0.00 sys = 8.98 CPU) @ 222.72/s (n=2000)
--------------------
Rate HoH refHoH
HoH 200/s -- -10%
refHoH 223/s 11% --
----------------------------------------
Benchmark: timing 100 iterations of HoHoH, refHoHoH...
HoHoH: 30 wallclock secs (28.75 usr + 0.01 sys = 28.76 CPU) @ 3.48/s (n=100)
refHoHoH: 24 wallclock secs (22.49 usr + 0.00 sys = 22.49 CPU) @ 4.45/s (n=100)
--------------------
Rate HoHoH refHoHoH
HoHoH 3.48/s -- -22%
refHoHoH 4.45/s 28% --
----------------------------------------
</code>
<br><br>
-Lee
<br><br>
"To be civilized is to deny one's nature."