6

Consider the following Perl 6 script skeleton:

my regex perlish    { .*[ea]?[ui]? rl $ }
my Str @words = '/usr/share/dict/words'.IO.lines;

for @words -> $word {
    ...
}

base idea for the code in this question from the perl6 website's examples.

My /usr/share/dict/words is an indirect symbolic link to /usr/share/dict/american-english. It's 99,171 lines long, with one word/line.

For comparison's sake, Python 3 does 100 loops of the below in a total of 32 seconds: that's just 0.32942s / loop.1

Here are the things I've tried putting in place of the stub code, with their benchmark times as noted:

  • "Inline" if100 loops, average 9.74219s / loop, totalling 16 min 14.219s

    say "$word probably rhymes with Perl" if $word ~~ /<perlish>/;
    say "$word is a palindrome" if $word eq $word.flip && $word.chars > 1;
    
  • Short Circuit (not ternary) — 10 loops, average 6.1925s / loop, normalised to totalling +/- 10.3 min

    $word eq $word.flip  && $word.chars > 1 && say "$word is a palindrome";
    $word ~~ /<perlish>/ && say "$word probably rhymes with Perl";
    
  • given/when (switch/case) — 100 loops, average 6.18568s / loop totalling 10 min 18.568s

    given $word {
      when /<perlish>/ 
        { say "$word probably rhymes with Perl"; proceed; }
      when $word eq $word.flip && $word.chars > 1 
        { say "$word is a palindrome"; proceed; }
    }
    
  • "normal" if block — 100 loops, average 6.0588s / loop totalling 10 min 5.880s

    if $word eq $word.flip && $word.chars > 1 { say "$word is a palindrome"; }
    if $word ~~ /<perlish>/ { say "$word probably rhymes with Perl"; }
    

Somewhat unsurprisingly, the normal if block is fastest. But, why is the inline if (what the website uses for an example) so much slower?


1 I'm not saying Perl 6 is slow... but I thought Python was slow and... wow. Perl 6 is slow... ignoring multithreading, parallelism and concurrency, all of which are built in by Perl 6 and which Python leaves much to be desired.


Specs: Rakudo version 2015.12-219-gd67cb03 on MoarVM version 2015.12-29-g8079ca5 implementing Perl 6.c on a 2.2GHz QuadCore Intel Mobile i7 with 6GB of RAM.

I ran the tests like time for i in ``seq 0 100``; do perl6 --optimize=3 words.pl6; done.

cat
  • 3,888
  • 5
  • 32
  • 61
  • 1
    Yes, Perl6 is currently a pig. A very slow one. – vonbrand Jan 27 '16 at 02:32
  • 2
    On Perl 6 being slow: "we'll be spending much of our effort on optimizing in 2016". http://doc.perl6.org/language/faq#Is_Perl_6_fast_enough_for_me? – CIAvash Jan 27 '16 at 07:53
  • 2
    @CIAvash [FAQ: Is Perl 6 fast enough for me?](http://doc.perl6.org/language/faq#Is_Perl_6_fast_enough_for_me%3F). I don't know why having a question mark at the end of a link in a comment breaks it, but this link takes you right to that section instead of the top of the page. – Christopher Bottoms Jan 27 '16 at 19:57
  • @ChristopherBottoms it seems like http://perl6.org has issues with section headers `#` on many pages. – cat Jan 27 '16 at 20:05
  • @cat StackOverflow isn't including the final `?` in the automatically created link. I can cut and paste 'doc.perl6.org/language/faq#Is_Perl_6_fast_enough_for_me?' into the browser and it works fine, but try clicking on http://doc.perl6.org/language/faq#Is_Perl_6_fast_enough_for_me? – Christopher Bottoms Jan 27 '16 at 20:11
  • @cat neither way works for me, but section headers work on other sites for me. I dunno. – cat Jan 27 '16 at 20:17

2 Answers2

10

(This page became the p6doc Performance page.)

Dealing with Perl 6 speed issues

I don't know why the statement modifier form of if is slower. But I can share things that can help folk deal with Perl 6 speed issues in general so I'll write about those, listed easiest first. (I mean easiest things for users and potential users to do, not easiest for compiler devs.)

Why does the speed of your code matter?

I recommend you share your answer to these higher level questions:

  • How much faster would your code need to run to make a worthwhile difference? Could the full speed up wait another month? Another year?

  • Are you exploring Perl 6 for fun, assessing its potential long term professional relevance to you, and/or using it in your $dayjob?

Wait for Rakudo to speed up

5 years ago Rakudo was 1,000 times slower or more for some operations. It's been significantly speeding up every year for years even though speeding it up was explicitly not the #1 dev priority. (The mantra has been "make it work, make it work right, make it fast". 2016 is the first year in which the "make it work fast" aspect is truly in the spotlight.)

So, imo, one sensible option if the Rakudo Perl 6 compiler is really too slow for what you want to do, is to wait for others to make it faster for you. It could make sense to wait for the next official release (there's at least several each year) or wait a year or three depending on what you're looking for.

Visit the freenode IRC channel #perl6

Compiler devs, the folk who best know how to speed up Perl 6 code, aren't answering SO questions. But they are generally responsive on #perl6.

If you don't get all the details or results you want from here then your best bet is to join the freenode IRC channel #perl6 and post your code and timings. (See next two headings for how best to do that.)

Profile code snippets

Rakudo on MoarVM has a built in profiler:

$ perl6 --profile -e 'say 1'
1
Writing profiler output to profile-1453879610.91951.html

The --profile option is currently only for micro-analysis -- the output from anything beyond a tiny bit of code will bring your browser to its knees. But it could be used to compare profiles of simple snippets using if conventionally vs as a statement modifier. (Your regex using examples are almost certainly too complex for the current profiler.)

Profiling results may well mean little to you without help and/or may point to confusing internal stuff. If so, please visit #perl6.

Write faster Perl 6 code, line by line

Your immediate focus seems to be the question of why one way of writing a line of code is slower than another way. But the flipside of this "academic" question is the practical one of writing faster lines of code.

But if someone's a Perl 6 newbie, how are they going to know how? Asking here is one way but the recommended approach is visiting #perl6 and letting folk know what you want.

#perl6 has on-channel evalbots that help you and others investigate your issue together. To try code snippets out publicly enter m: your code goes here. To do so privately write /msg camelia m: your code goes here.

For simple timing use variations on the idiom now - INIT now. You can also generate and share --profile results easily using a #perl6 evalbot. Just join the channel and enter prof-m: your code goes here.

Write faster Perl 6 code by refactoring

  • Use better algorithms, especially parallel/concurrent ones.

  • Use native arrays (eg Array[int8] for an array of 8 bit integers) for compact, faster number crunching.

For more info about doing this, visit #perl6.

Use (faster) foreign code

  • Use NativeCall wrappers for C libs such as Gumbo or for C++ libs (experimental). NativeCall itself is currently poorly optimized but that's set to change in 2016 and for many applications the NativeCall overhead is a small part of performance anyway.

  • Inline::Perl5 builds on NativeCall to enable use of Perl 5 in Perl 6 (and vice-versa) including arbitrary Perl 5 code and high-performance Perl 5 XS modules. This interop allows passing integers, strings, arrays, hashes, code references, file handles and objects between Perl 5 and Perl 6; calling methods on Perl 5 objects from Perl 6 and calling methods on Perl 6 objects from Perl 5; and subclassing Perl 5 classes in Perl 6.

(There are similar but less mature or even alpha variants for other langs like Inline::Python, Inline::Lua and Inline::Ruby.)

Review benchmarks

The best relevant benchmarking tool I know of is perl6-bench which compares various versions of Perl with each other including various versions of both Perl 5 and Perl 6.

There may already be benchmarks contrasting a regular if statement and a statement modifier form if statement but I doubt it. (And if not, you would be making a nice contribution to Perl 6 if you wrote an extremely simple pair of snippets and got them added to perl6-bench.)

Help speed Rakudo up

The Rakudo Perl 6 compiler is largely written in Perl 6. So if you can write Perl 6, then you can hack on the compiler, including optimizing any of the large body of existing high-level code that impacts the speed of your code.

Most of the rest of the compiler is written in a small language called NQP that's almost just a subset of Perl 6. So if you can write Perl 6 you can fairly easily learn to use and improve the middle-level NQP code too.

Finally, if low-level C hacking is your idea of fun, checkout MoarVM.

Community
  • 1
  • 1
raiph
  • 31,607
  • 3
  • 62
  • 111
  • 1
    This is a spectacular answer, and far more in-depth than I was expecting! Personally, I'm only interested in why these syntax elements are internally different and thus slower, but the tips are useful anyways. – cat Jan 27 '16 at 19:27
  • Yeah, it's OTT but now I can refer to it. – raiph Jan 27 '16 at 19:44
  • It would make more sense to me if the inline (statement modifier) form was faster because it eliminates a lexical scope. My guess would be it just reflects the currently relatively poor state of optimization. – raiph Jan 27 '16 at 19:45
  • Exactly: as in http://stackoverflow.com/questions/3255512/why-are-function-calls-in-perl-loops-so-slow , perl is faster when it doesn't save state & jump. – cat Jan 27 '16 at 19:47
  • The Perl 6 language design/architecture has relatively little to do with Perl 5's. And the Rakudo Perl 6 compiler has absolutely nothing in common with it (other than its use of Perl 5 for some minor peripheral things like build scripts). So don't assume that anything that's ever been said about anything to do with Perl 5's internals has any relevance to Perl 6. – raiph Jan 27 '16 at 19:51
  • Perhaps it's also a good idea to mention that you can use NativeCall when you need extra performance (in other words your hot code can be written in C while you still get all of the benefits of Perl 6 as a glue language) – Aleks-Daniel Jakimenko-A. Jan 28 '16 at 21:58
  • @Aleks-DanielJakimenko-A. Rewritten to fix some stuff, incorporate your suggestion, and hopefully serve as a solid general answer to speed questions. – raiph Jan 29 '16 at 06:36
  • @raiph this answer is great, what about getting it into the docs? – Aleks-Daniel Jakimenko-A. Jan 29 '16 at 12:49
3

I had a different answer before, which was based on a piece of code I accidentally left in in between benchmark runs.

given this benchmark code:

my regex perlish { [ea?|u|i] rl $ }
my Str @words = '/usr/share/dict/words'.IO.lines;

multi sub MAIN('postfixif') {
    for @words -> $word {
        say "$word probably rhymes with Perl" if $word ~~ / [ea?|u|i] rl $ /;
        say "$word is a palindrome" if $word eq $word.flip && $word.chars > 1;
    }
}

multi sub MAIN('prefixif') {
    for @words -> $word {
        if $word ~~ /[ea?|u|i] rl $ / { say "$word probably rhymes with Perl" };
        if $word eq $word.flip && $word.chars > 1 { say "$word is a palindrome" };
    }
}

multi sub MAIN('postfixif_indirect') {
    for @words -> $word {
        say "$word probably rhymes with Perl" if $word ~~ / <perlish> /;
        say "$word is a palindrome" if $word eq $word.flip && $word.chars > 1;
    }
}

multi sub MAIN('prefixif_indirect') {
    for @words -> $word {
        if $word ~~ / <perlish> / { say "$word probably rhymes with Perl" };
        if $word eq $word.flip && $word.chars > 1 { say "$word is a palindrome" };
    }
}

multi sub MAIN('shortcut') {
    for @words -> $word {
        if $word.ends-with('rl') && $word ~~ / [ea?|u|i] rl $ / { say "$word probably rhymes with Perl" };
        if $word eq $word.flip && $word.chars > 1 { say "$word is a palindrome" };
    }
}

I get the following results:

  3x postfixif_indirect:    real    1m20.470s
  3x  prefixif_indirect:    real    1m21.970s

  3x          postfixif:    real    0m50.242s
  3x           prefixif:    real    0m49.946s

  3x           shortcut:    real    0m8.077s

The postfixif_indirect code corresponds to your "Inline" if, the prefixif_indirect code corresponds to your "normal" if block. The ones without "_indirect" just have the regex itself in the if statement rather than indirectly called as <perlish>.

As you can see, the speed difference between regular if blocks and postfix if is barely measurable on my machine. But also, I was measuring against a different file from yours. Mine has 479.828 lines, so you can't directly compare the timings anyway.

However, a quick glance over the profile output from perl6 --profile pointed out that 83% of total time was spent in ACCEPTS (which is the method that implements the smart match operator ~~) or in things called by it.

What tipped me off to the fact that the indirect call to perlish may be expensive was that the time spent inside perlish was only 60%. So about 23% of time was spent doing some sort of setup work before perlish could even start matching against the string. Pretty bad, I admit. Surely, this'll be a good target for optimization.

But the biggest gain was adding a short-circuiting check just to see if the string ends in "rl". This gets our code down to 10% of what it used to take.

Our regex engine surely deserves a whole lot more optimization. Potentially, if a regex can be statically known to only ever match if the target string starts with or ends in a specific substring, it could have a check emitted up-front so that none of the setup work has to be done in the "failure to match" case.

We'll definitely see what 2016 will bring. I'm already excited for sure!

EDIT: Even though i used "for i in seq 0 100, that only executes things three times on my machine. I have no clue what's up with that, but I corrected the timing lines to say 3x instead of 100x.

cat
  • 3,888
  • 5
  • 32
  • 61
timotimo
  • 4,299
  • 19
  • 23
  • Wow, your `words` file is short! Before posting this question, I actually tried the palindrome testing using a regex and it was ***painfully*** slow. I'm sure you're right about optimisations, though, and I'm anxious to see what'll happen! – cat Jan 31 '16 at 05:06
  • I don't understand why you say my *words* file is short. Based on your description, it's almost 5x more lines. But in general, performance in Perl 6 is all over the place. Sometimes you end up really massaging a bad spot, sometimes you hit all the optimized paths. Fortunately, there's multiple changes in the queue that'll make basically everything faster. – timotimo Feb 01 '16 at 03:04
  • As a North American, I am *really* not used to using `.` as a thousands separator, whereas you would find using `,` as a thousands separtor instead of `.` to be odd, and I was really tired when I read this answer last night, sorry. – cat Feb 01 '16 at 03:07
  • It takes your Rakudo only 8 seconds to process 500 thousand lines 100 times? That's 50 million lines, do you own a supercomputer? – cat Feb 01 '16 at 03:10
  • 1
    Ah, OK. I live in Germany, where . is the thousands separator and , is the decimal separator. I'm obviously not yet consistent about it yet. – timotimo Feb 01 '16 at 03:10
  • Um. huh. I ran your for loop and it only ended up executing the program 3 times. What's up with that? – timotimo Feb 01 '16 at 03:13
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/102190/discussion-between-timotimo-and-cat). – timotimo Feb 01 '16 at 03:16