I often use very deeply nested structures in my code. This can make for ugly code.
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).
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.
I thought wrong.
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.
I don't do a lot of benchmarking so if someone sees a flaw in my method, I'd appreciate feedback.
Lesson: So not only does using refs to nested data structures make your code easier to read, it makes it a good bit faster.
#!/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 %ha
+sh ) { $hash{$k}++ } },
ref=>sub { foreach my $k ( keys %{$shortref} ) { $shortre
+f->{$k}++ } } },
{count=> $iterations, ref2hash1=>sub { foreach my $k ( key
+s %{$ref1->{key1}}) { $ref1->{key1}{$k}++ }},
shortref1=>sub { foreach my $k ( keys %{$shortref1}) { $s
+hortref1->{$k}++ }} },
{count=> $iterations, ref2hash2=>sub { foreach my $k ( key
+s %{$ref2->{key1}{key2}}) { $ref2->{key1}{key2}{$k}++ }},
shortref2=>sub { foreach my $k ( keys %{$shortref2}) { $s
+hortref2->{$k}++ }} },
{count=> $iterations, ref2hash3=>sub { foreach my $k ( key
+s %{$ref3->{key1}{key2}{key3}}) { $ref3->{key1}{key2}{key3}{$k}++ }},
shortref3=>sub { foreach my $k ( keys %{$shortref3}) { $s
+hortref3->{$k}++ }} },
{count=> $iterations, ref2hash4=>sub { foreach my $k ( key
+s %{$ref4->{key1}{key2}{key3}{key4}}) { $ref4->{key1}{key2}{key3}{key
+4}{$k}++ }},
shortref4=>sub { foreach my $k ( keys %{$shortref4}) { $s
+hortref4->{$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) @ 48
+54.37/s (n=10000)
ref: 2 wallclock secs ( 2.18 usr + 0.00 sys = 2.18 CPU) @ 45
+87.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) @ 40
+48.58/s (n=10000)
shortref1: 3 wallclock secs ( 2.20 usr + 0.00 sys = 2.20 CPU) @ 45
+45.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) @ 34
+48.28/s (n=10000)
shortref2: 2 wallclock secs ( 2.14 usr + 0.00 sys = 2.14 CPU) @ 46
+72.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) @ 29
+85.07/s (n=10000)
shortref3: 3 wallclock secs ( 2.23 usr + 0.00 sys = 2.23 CPU) @ 44
+84.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) @ 27
+93.30/s (n=10000)
shortref4: 2 wallclock secs ( 2.16 usr + 0.00 sys = 2.16 CPU) @ 46
+29.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) @ 19
+9.80/s (n=2000)
refHoH: 9 wallclock secs ( 8.98 usr + 0.00 sys = 8.98 CPU) @ 22
+2.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% --
----------------------------------------
-Lee
"To be civilized is to deny one's nature."