为什么内联 if 语句平均比其他类型的 if 慢至少三分之一?

Why are inline if statements an average of at least one-third slower than other types of if?

考虑以下 Perl 6 脚本框架:

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

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

此问题中代码的基本思路来自 the perl6 website's examples

我的 /usr/share/dict/words 是 link 到 /usr/share/dict/american-english 的间接符号。它有 99,171 行长,其中有一个 word/line。

为了比较,Python 3 does 100 loops of the below in a total of 32 seconds: that's just 0.32942s / loop.1

以下是我尝试用它们代替存根代码的东西,它们的基准时间已注明:

不出所料,普通的 if 块是最快的。但是,为什么内联 if(网站使用的例子)这么慢?


1 我并不是说 Perl 6 很慢……但我认为 Python 很慢而且。 .. 哇。 Perl 6 很慢...忽略多线程、并行性和并发性,所有这些都是 Perl 6 内置的,Python 留下 很多 不尽如人意。


规格:Rakudo version 2015.12-219-gd67cb03 on MoarVM version 2015.12-29-g8079ca5 implementing Perl 6.c 2.2GHz 四核英特尔移动 i7 处理器,6GB 内存。

我 运行 像 time for i in ``seq 0 100``; do perl6 --optimize=3 words.pl6; done 这样的测试。

(本页变成了p6doc Performance page。)

处理 Perl 6 速度问题

不知道为什么if的语句修饰符形式比较慢。但我可以分享一些可以帮助人们处理一般 Perl 6 速度问题的东西,所以我会写这些,最简单的列在最前面。 (我的意思是用户和潜在用户最容易做的事情,而不是编译器开发人员最容易做的事情。)

为什么代码速度很重要?

我建议您分享对这些更高级别问题的回答:

  • 您的代码需要多快才能运行产生有价值的变化?全面提速能再等一个月吗?又一年?

  • 您探索 Perl 6 是为了乐趣,评估它与您的潜在长期专业相关性,and/or 在您的 $dayjob 中使用它吗?

等乐道加速

5 年前,Rakudo 的某些操作要慢 1,000 倍甚至更多。多年来,它每年都在显着加速,尽管加速显然 不是 #1 开发优先级。 (口头禅是 "make it work, make it work right, make it fast"。2016 年是 "make it work fast" 方面真正成为焦点的第一年。)

所以,imo,如果 Rakudo Perl 6 编译器对于你想要做的事情来说真的太慢了​​,一个明智的选择是等待其他人为你让它更快。等待下一个正式版本(每年至少有几个)或等待一年或三年可能是有意义的,具体取决于您要寻找的内容。

访问 freenode IRC 频道#perl6

编译器开发人员,最了解如何加速 Perl 6 代码的人,没有回答 SO 问题。但他们通常对#perl6 有响应。

如果您没有从此处获得所需的所有详细信息或结果,那么最好的办法是加入 the freenode IRC channel #perl6 和 post 您的代码和时间安排。 (请参阅接下来的两个标题,了解如何最好地做到这一点。)

个人资料代码片段

MoarVM 上的 Rakudo 有一个内置的分析器:

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

--profile 选项目前仅适用于 micro-analysis -- 任何超过一小段代码的输出都会让您的浏览器崩溃。但它可用于比较使用 if 的简单片段的配置文件,传统上与​​作为语句修饰符。 (您使用示例的正则表达式对于当前的分析器来说几乎肯定太复杂了。)

如果没有帮助,分析结果对您来说意义不大 and/or 可能会导致内部内容混乱。如果是这样,请访问#perl6.

逐行编写更快的 Perl 6 代码

您的直接关注点似乎是为什么一种编写代码行的方式比另一种方式慢的问题。但是这个 "academic" 问题的另一面是编写更快的代码行的实际问题。

但是如果某人是 Perl 6 新手,他们怎么知道呢?在这里询问是一种方法,但推荐的方法是访问 #perl6 并让人们知道您想要什么。

#perl6 有 on-channel 个评估机器人,可以帮助您和其他人一起调查您的问题。要公开试用代码片段,请输入 m: your code goes here。为此,请私下写信 /msg camelia m: your code goes here.

对于简单的计时使用习语 now - INIT now 的变体。您还可以使用 #perl6 evalbot 轻松生成和共享 --profile 结果。只需加入频道并输入 prof-m: your code goes here.

通过重构编写更快的 Perl 6 代码

  • 使用更好的算法,尤其是 parallel/concurrent 算法。

  • 使用本机数组(例如,Array[int8] 用于 8 位整数数组)以实现紧凑、更快的数字运算。

有关执行此操作的更多信息,请访问#perl6。

使用(更快)外国代码

  • 使用 NativeCall wrappers for C libs such as Gumbo 或用于 C++ 库(实验)。 NativeCall 本身目前优化不佳,但这种情况将在 2016 年发生变化,对于许多应用程序而言,NativeCall 开销只是性能的一小部分。

  • Inline::Perl5 建立在 NativeCall 的基础上,可以使用 Perl 5 in Perl 6 (and vice-versa) 包括任意 Perl 5 代码和 high-performance Perl 5 XS 模块。这个互操作允许在 Perl 5 和 Perl 6 之间传递整数、字符串、数组、散列、代码引用、文件句柄和 objects;从 Perl 6 调用 Perl 5 objects 的方法,从 Perl 5 调用 Perl 6 objects 的方法;并在 Perl 6.

  • 中继承 Perl 5 类

(像 Inline::Python, Inline::Lua and Inline::Ruby 这样的其他语言也有类似但不太成熟甚至是 alpha 变体。)

审查基准

我知道的最相关的基准测试工具是 perl6-bench,它可以将不同版本的 Perl 相互比较,包括 Perl 5 和 Perl 6 的不同版本。

可能已经有对比常规 if 语句和语句修饰符形式 if 语句的基准,但我对此表示怀疑。 (如果没有,如果你编写了一对非常简单的代码片段并将它们添加到 perl6-bench,你将对 Perl 6 做出很好的贡献。)

帮助加快 Rakudo 的速度

Rakudo Perl 6 编译器主要是用 Perl 6 编写的。因此,如果您可以编写 Perl 6,那么您就可以破解编译器,包括优化现有 high-level 的任何大型 body ] 影响代码速度的代码。

编译器的大部分其他部分都是用一种叫做 NQP 的小语言编写的,它几乎只是 Perl 6 的一个子集。因此,如果你能编写 Perl 6,你就可以相当容易地学习使用和改进middle-level NQP 代码也是。

最后,如果 low-level C hacking 是您的乐趣所在,请查看 MoarVM

我之前有一个不同的答案,它基于我在基准测试运行之间不小心留下的一段代码。

鉴于此基准代码:

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" };
    }
}

我得到以下结果:

  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

postfixif_indirect代码对应你的"Inline"if,prefixif_indirect代码对应你的"normal"if块。那些没有“_indirect”的只是在 if 语句中有正则表达式本身,而不是间接调用 <perlish>.

如您所见,常规 if 块和后缀 if 之间的速度差异在我的机器上几乎无法测量。而且,我正在根据与您不同的文件进行测量。 我的有 479.828 行,所以你不能直接比较时间。

然而,快速浏览一下 perl6 --profile 的配置文件输出会发现总时间的 83% 花在了 ACCEPTS(也就是实现智能匹配运算符的方法~~)或者在它调用的东西里

让我知道间接调用 perlish 可能很昂贵的事实是,在 perlish 中花费的时间仅为 60%。因此,在 Perlish 甚至可以开始匹配字符串之前,大约 23% 的时间花在了一些设置工作上。很糟糕,我承认。当然,这将是一个很好的优化目标。

但最大的收获是添加了一个短路检查,只是为了查看字符串是否以 "rl" 结尾。这使我们的代码减少到过去的 10%。

我们的正则表达式引擎确实值得更多优化。潜在地,如果可以静态地知道正则表达式仅在目标字符串以特定子字符串开头或结尾时才匹配,则它可以预先发出检查,以便必须完成 none 的设置工作在 "failure to match" 案例中。

我们一定会看到 2016 年会带来什么。我已经很兴奋了!

编辑:即使我在 seq 0 100 中使用了“for i”,它在我的机器上只执行了三次。我不知道这是怎么回事,但我更正了时间线,改为说 3x 100 倍。