Benchmarking in Perl: Map versus For Loop
Last week, I was coding in Perl for an Interchange project. I’ve been in and out of Perl and Ruby a lot lately. While I was working on the project, I came across the following bit of code and wanted to finally sit down and figure out how to use the map function in Perl on this bit of code.
my @options;
for my $obj (@$things) {
push @options, {
value => $obj->{a},
label => $obj->{b}
};
}
return \@options;
I’m a big fan of Ruby’s inject method and in general a fan of the Enumerable Module, but I have a brain block when it comes to using the map method in both Perl and Ruby. I spent a little time investigating and working on a small local Perl script to test the implementation of the map method. I came up with the following:
return [ map {
{
value => $_->{a},
label => $_->{b}
}
} @$things ];
After that, I wanted to make sure the code change was justified. The Interchange application that is the source of this code is built for performance, so I wanted to ensure this change didn’t hinder performance. It’s been a while since I’ve done benchmarking in Perl, so I also had to refresh my memory regarding using the Benchmark module. I came up with:
#!/usr/bin/perl
use Benchmark;
my $count = 1000000;
my $things = [
{'a' => 123, 'b' => 456, 'c' => 789 },
{'a' => 456, 'b' => 789, 'c' => 123 }
];
#Test definitions as methods to mimic use in application
my $test1 = sub {
my @options;
for my $obj (@$things) {
push @options, {
value => $obj->{a},
label => $obj->{b}
};
}
return \@options;
};
my $test2 = sub {
return [ map {
{
value => $_->{a},
label => $_->{b}
}
} @$things ];
};
#Benchmark tests & results.
$t0 = Benchmark->new;
$test1->() for(1..$count);
$t1 = Benchmark->new;
$td = timediff($t1, $t0);
print "the code for test 1 took:",timestr($td),"\n";
$t0 = Benchmark->new;
$test2->() for(1..$count);
$t1 = Benchmark->new;
$td = timediff($t1, $t0);
print "the code for test 2 took:",timestr($td),"\n";
The results were:
Test # | Before (For Loop) | After (Map) |
1 | 5 sec | 4 sec |
2 | 5 sec | 4 sec |
3 | 5 sec | 5 sec |
4 | 5 sec | 5 sec |
5 | 6 sec | 4 sec |
6 | 6 sec | 4 sec |
7 | 6 sec | 4 sec |
8 | 5 sec | 5 sec |
9 | 5 sec | 4 sec |
10 | 5 sec | 4 sec |
Average | 5.3 sec | 4.3 sec |
In this case, replacing the imperative programming style here with Functional programming (via map) yielded a small performance improvement, but the script executed each method 1,000,000 times, so the performance gain yielded by just one method call is very small. I doubt it’s worth it go on a code cleanup rampage to update and test this, but it’s good to keep in mind moving forward as small bits of the code are touched. I also wonder if the performance will vary when the size of $things changes — something I didn’t test here. It was nice to practice using Perl’s map method and Benchmark module. Yippee.
Comments