Perl 5.19.x performance improvements
Perl 5.19.9 was released today, and it includes some exciting new features like subroutine signatures, and postfix dereferencing.
The 5.19.X line also includes a number of optimisations that I'd like to discuss briefly, without going into too much detail (I hope).
Combined and/or operators in void context
The first improvement was added back in 5.19.6, and effects and and or operators in void context. It improves the short-cirtcuiting logic of and/or operations when inside of an if statement.
What this means is that in many cases, this:
if ($var1 || $var2) { ... }
will be ever-so-slightly faster if $var1 is true than it used to, and
unless ($var1 && $var2) { ... }
will be ever-so-slightly faster if $var1 is false. This is about a 2% speed improvement in my tests, for each of the above constructs being evaluated.
return at end of sub optimised out
This improvement was added in 5.19.8, and optimises out a return statement at the end of a sub so that:
return $x;
is compiled as
$x;
which is a fair bit faster (about 10%) since a return statement actually includes a few more ops behind the scenes than necessary for these cases.
Multiple single PADOPs combined into a list
The final optimisation was just released with 5.19.9 and turns multiple single PADOPs into its list form.
That means this:
my $x;
my $y;
becomes this:
my ($x, $y);
Which then can be optimised into a single PADRANGE op which was added back in 5.18.0.
This gives a speedup of about 7% in the above case, and will have a greater effect if more PADOPS (my $x; my @y; my %z) are used in a row.
Testing
For light testing, I used a best-case scenario subroutine for all above optimisations, and enabled each in turn to see how the sub improved. (I didn't test them individually, so numbers may vary. IE: I enabled return optimisations, then padrange optimisations, then and/or in void context optimisations).
Using this program:
#!/usr/bin/perl
use strict;
use warnings;
my $aa = 1;
my $bb = 0;
sub somesub {
my $x;
my $y;
if ($aa || $bb) {
$x = 1;
}
return $x;
}
for (1..50_000_000) {
somesub();
}
I ran it 3 times for each test using "time ./opt.pl" (I know, start up costs skew results, but only ever so slightly in comparison to what was being tested in this case, and yes, 3 is not enough to give a really good average...), and averaged out the results:
Original: 6.69s
Optimise out return: 6.03s 10% speedup
Optimise my $x; my $y; into my ($x; $y);: 5.62s 7% speedup
Optimise if ($a || $b) { ... } if $a is true: 5.53s 2% speedup
Total: 19% speedup
I'm pretty happy with the results.
Playing with Perl's optimiser was a lot of fun (and a fair amount of frustration :)), and I hope to do more work in this area.
Perl 5.20 (which is set to release May of this year) will includes these optimisations, and I hope to have many more for 5.22 next year.
Cheers,
-- Matthew Horsfall (alh)
P.S. If anyone wants more in-depth details of the optimisations, I'm happy to provide them. (Separate blog posts?)
I think this material is interesting and would be interested to hear more about it.
Also, maybe you could link to the commits where the changes were added. I think some people would be interested.
Is there any more info about subroutine signatures?
They are documented in perlsub in the new release: https://metacpan.org/pod/release/TONYC/perl-5.19.9/pod/perlsub.pod#Signatures
Looks to me like we need structured exceptions before subroutine signatures comes out of experimental.
That's funny, I would have thought Perl was already making the return cases the same. Slap me with a Sufficiently Optimized Compiler Fallacy, I guess.
Why do you say that? Not that structured exceptions are a bad idea, but why are they any more important for signatures than any other part of the language?
Mmmm. Signatures are yummy.
Thanks a lot for working on this.
It's not clear from your post if the first optimization only applies to variables being tested by and/or operators, would it apply as well to e.g.
(x() && y())
(although obviously there would be less gain due to subroutine call overhead), or to more complex forms like(($x || $y) && ($i || j))
?Can you explain the "return at end of sub" thing a bit more? Why would an explicit return have more ops?
"return" is a control flow operation, it has to arrange to *leave* the sub from wherever it is. That's additional work compared to just leaving a value on the stack.
Alright, I'll update the blog post when I get a chance and write another more detailed explanation of the changes.
Thanks.
-- Matthew Horsfall (alh)
Ævar/stu42j, I'll cover those when I write the other posts.