Limbic~Region has asked for the wisdom of the Perl Monks concerning the following question:

All,
Way back in 2003, I wrote Inline subs? asking how hard it would be to inline subroutines in perl. Over the years, I learned not to worry about it and to write code for maintainability first and to only worry about optimizations if there was a real testable performance requirement that wasn't being met.

I just wrote a rapid prototype of an application that really should have been written in C/C++ or at the very least - many things in XS instead of pure perl. While I am happy with the prototype and can use it effectively as an off-line analysis tool, I would like to see if there are any low effort changes that I can make to give it a significant speed improvement. Yes, I have profiled. Yes, I know that traditional answers such as using 'a better algorithm', 'caching' or 'a faster language'. I have already optimized the things I can at the level of effort I am willing to invest.

In other words, this post is about using source filters (possibly Filter::Simple::Compile) to inline subs and not the traditional performance tuning dogma.

Inlining subs is not easy as they can have multiple exit points, call other subs, etc. I am not trying to make every sub inlininable. Here is outline of what I am currently thinking.

Sub Definition

Subs to be inlined will need to meet the following regex

m|^inline sub (\w+)(.*?^})|sm inline sub example { my ($num, $den) = @_; if (! $den) { return; } return $num / $den; }
In other words, the inline keyword is at the beginning of the line and the end of the sub is a close curly brace at the beginning of the line.

Assignment and Return Variable Names

Unintended behavior will result if you do the following:

my $date = calc_date(); inline sub calc_date { my $date = strftime('%Y-%m-%d', localtime); return $date; }
It is because it ultimately gets translated to something like:
my $date; SOMEUNIQUELABEL: { my $date = strftime('%Y-%m-%d', localtime); $date = $date; last SOMEUNIQUELABEL; }

Sub Invocation Line

Supported flavors:

void_sub($foo, $bar); my @result = list_context_sub($foo, $bar); $val = scalar_context_sub($foo, $bar);
Parens are required even with no arguments. There can only be trailing white space after the semicolon (not even comments). The right hand side can only be a single sub invocation and not multiple values. No postfix conditionals are supported. It should be possible to turn a postfix conditional into block form but for now that is a level of complexity not supported (KISS). The calling line is replaced with an empty line if the invoking line has no my'd variables. If there are my'd variables, the = is replaced with a ;
my ($foo, $bar) = blah($asdf); # becomes my ($foo, $bar);

Sub Replacement

The subroutine will become a naked block with a label.

my $blah = foo(1, 2); sub foo { my ($var1, $var2) = @_; return $var1 + $var2; } # becomes my $blah; SOMEUNIQUELABEL: { my ($var1, $var2) = @_; return $var1 + $var2; }

Argument Passing

Supported flavors

void - no reference to @_ explicitly or implicitly my ($var1, $var2) = @_; # consumed all at once, no more references to +@_ explicitly or implicitly
@_ is replaces with the calling arguments from the invocation line
my $blah = foo(1, 2); sub foo { my ($var1, $var2) = @_; return $var1 + $var2; } # becomes my $blah; SOMEUNIQUELABEL: { my ($var1, $var2) = (1, 2); return $var1 + $var2; }

Return Values

Supported flavors

return; # void return $foo; # single item return ($blah, $asdf); # multiple items
No implicit return is supported (last evaluated statement). No postfix conditionals are supported. It should be possible to turn a postfix conditional into block form but for now that is a level of complexity not supported (KISS). The return line gets replaced with the applicable assignment and last. The only special exception is if you have an empty return, the assignment gets replaced with undef;
my $blah = foo(1, 2); sub foo { my ($var1, $var2) = @_; if ($var2 == 0) { return; } return $var1 + $var2; } # becomes my $blah; SOMEUNIQUELABEL: { my ($var1, $var2) = (1, 2); if ($var2 == 0) { $blah = undef; last SOMEUNIQUELABEL; } $blah = $var1 + $var2; last SOMEUNIQUELABEL; }

I considered a do block as well but that would require the subs to all be written in the form where the return value was implicit (last evaluated expression). Any thoughts? Any gotchas or drawbacks I am not seeing? I know that the introduction of the new scope doesn't come for free either. This seems like a change I could implement quickly and Benchmark.

Update: As I implement this, I am adding additional constraints using <ins>comment</ins> tags.

Cheers - L~R

Replies are listed 'Best First'.
Re: Inline Subs Revisited
by BrowserUk (Patriarch) on Apr 28, 2010 at 15:54 UTC

    I think if you are going to do this, then you should avoid trying to emulate what a subroutine does (and can do) quite so slavishly.

    Whilst you will gain some performance using your inlined block over a sub call--around 61% (b .v. c) on the evidence of the simple sub below; it will lessen the more you do in the sub.

    You gain more by simply avoiding the copying of the parameters into named vars within the sub. (c .v. a) 71%.

    But if you're going to inline, why not eliminate the block and the copying by substituting the parameters directly into the body of the sub and placing that directly inline? This gives (d .v. c) 750% improvement (for this trivial sub):

    sub s1{ $_[0] + $_[1] } sub s2{ my( $x, $y ) = @_; $x + $y; } cmpthese -1, { a=>q[ my $z = s1( 1, 2 ); ], b=>q[ my $z; { my( $x, $y ) = (1,2); $z = $x + $y } ], c=>q[ my $z = s2( 1, 2); ], d=>q[ my $z = 1 + 2; ], };; Rate c b a d c 1452758/s -- -38% -42% -88% b 2344698/s 61% -- -6% -81% a 2483364/s 71% 6% -- -80% d 12297390/s 746% 424% 395% --

    I realise that this limits the forms of the subs you can inline to fairly simple ones, but that's probably for the best anyway. 1) because it makes the parsing of the bodies simpler. 2) there are rapidly diminishing returns from inlining more complex subs.

    I did some experiments a few years ago to try and find out where the point of diminishing returns actually lay, but it is quite hard to come up with convincing generic guidelines. From memory, even a couple of nested blocks within the subroutine that declared local vars and you were in to the realms of uncertainty over whether any real gain was to be had or not. I'll try to resurrect an example from the backup CD of my old machine.


    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.
Re: Inline Subs Revisited
by Limbic~Region (Chancellor) on Apr 29, 2010 at 02:56 UTC
    All,
    In case anyone is interested, here is my very quick proof of concept: Here is an example of using it that I also used to test it. Over 5 million iterations, I have a consistent 3 second improvement from the inline version against a version where s/inline//g;

    Cheers - L~R

      I have a consistent 3 second improvement

      3 seconds saved on 463 seconds runtime hardly seems worth the effort. But you were never going to save much on the call overhead of subs that iterate over large arrays internally.

      You can easily save more than that by some slight rearrangements of the sub internals.

      And far more by using List::Util.


      Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
      "Science is about questioning the status quo. Questioning authority".
      In the absence of evidence, opinion is indistinguishable from prejudice.