Why are inline if statements an average of at leas

2019-06-19 03:23发布

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.

2条回答
老娘就宠你
2楼-- · 2019-06-19 03:26

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.

查看更多
放荡不羁爱自由
3楼-- · 2019-06-19 03:51

(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.

查看更多
登录 后发表回答