Recently motivated out of curiosity, I and a coworker were benchmarking different approaches to determining if a hash is empty in Perl. Expanding on this a little I present these curious results. In summary, it seems that there are roughly two code paths we’re hitting here. One for if (scalar %hash) and if (%hash), and another for if (keys %hash), if (values %hash), if (scalar keys %hash), and if (scalar values %hash). The latter is much faster when you hit a non-empty hash. It feels somewhat unfortunate that the simplest approach is (approximately) slowest.
hash size: 0 Rate if_scalar if if_values if_keys if_scalar_values if_scalar_keys if_scalar 3125000/s -- -3% -21% -23% -24% -24% if 3205128/s 3% -- -19% -21% -22% -22% if_values 3937008/s 26% 23% -- -2% -4% -5% if_keys 4032258/s 29% 26% 2% -- -2% -2% if_scalar_values 4098361/s 31% 28% 4% 2% -- -1% if_scalar_keys 4132231/s 32% 29% 5% 2% 1% -- hash size: 3 Rate if_scalar if if_keys if_scalar_keys if_values if_scalar_values if_scalar 967118/s -- -1% -86% -87% -87% -88% if 978474/s 1% -- -86% -86% -87% -88% if_keys 7142857/s 639% 630% -- -1% -7% -10% if_scalar_keys 7246377/s 649% 641% 1% -- -6% -9% if_values 7692308/s 695% 686% 8% 6% -- -3% if_scalar_values 7936508/s 721% 711% 11% 10% 3% -- hash size: 500 Rate if if_scalar if_scalar_values if_values if_scalar_keys if_keys if 912409/s -- -2% -88% -88% -88% -88% if_scalar 927644/s 2% -- -87% -88% -88% -88% if_scalar_values 7352941/s 706% 693% -- -1% -4% -4% if_values 7462687/s 718% 704% 1% -- -3% -3% if_scalar_keys 7692308/s 743% 729% 5% 3% -- -0% if_keys 7692308/s 743% 729% 5% 3% 0% --
From the script:
#!/usr/bin/env perl use strict; use warnings; use Benchmark qw(cmpthese); my %hash_empty = (); my %hash_small = (1..6); my %hash_large = (1..1000); foreach my $ref (\%hash_empty, \%hash_small, \%hash_large) { print 'hash size: ' . scalar(keys(%$ref)) . "\n"; cmpthese(5_000_000, { if => sub { if (%$ref) { 1 } else { 0 } }, if_scalar => sub { if (scalar %$ref) { 1 } else { 0 } }, if_keys => sub { if (keys %$ref) { 1 } else { 0 } }, if_values => sub { if (values %$ref) { 1 } else { 0 } }, if_scalar_keys => sub { if (scalar keys %$ref) { 1 } else { 0 } }, if_scalar_keys => sub { if (scalar keys %$ref) { 1 } else { 0 } }, if_scalar_values => sub { if (scalar values %$ref) { 1 } else { 0 } }, }); }