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?)

12 Comments

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?

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.

Leave a comment

About Matthew Horsfall (alh)

user-pic I blog about Perl.