32

每次我下载 Rakudo Perl 6 的新副本时,我都会运行以下表达式来了解其当前性能:

say [+] 1 .. 100000;

而且速度一直在增加,但每次计算都有明显的延迟(几秒钟)。作为比较,Perl 5(或其他解释语言)中的类似内容几乎立即返回:

use List::Util 'sum';

print sum(1 .. 100000), "\n";

或在 Ruby 中(也几乎是即时的):

(1 .. 100000).inject(0) {|sum,x| sum+x}

将表达式重写为 Perl6loop最终的速度大约是减小范围的两倍,但对于简单的计算来说仍然是一个非常明显的延迟(超过一秒):

my $sum;
loop (my $x = 1; $x <= 100000; $x++) {$sum += $x}

所以我的问题是,Perl6 实现的哪些方面导致了这些性能问题?这是否应该随着时间的推移而改善,或者这种开销是否是 Perl6 正在使用的“一切都是对象”模型的不幸副作用?

最后,loop构造比[+]归约运算符更快呢?我认为循环会导致比减少更多的总操作数。

编辑:

如果可以的话,我会同时接受mortiz' 和hobbs' 的答案。一切都被作为方法调用处理更直接地回答了为什么[+]慢,所以人们明白了。

4

5 回答 5

23

Rakudo 这么慢的原因有很多。

第一个也是最重要的原因是 Rakudo 还没有进行任何优化。当前的目标是更多地探索新功能,并变得更加健壮。你知道,他们说“先让它运行,然后让它正确,然后让它快速”。

第二个原因是 parrot 还没有提供任何 JIT 编译,垃圾收集器也不是最快的。有一个 JIT 编译器的计划,人们正在研究它(以前的编译器被淘汰了,因为它只是 i386 并且是维护的噩梦)。也有将 Rakudo 移植到其他 VM 的想法,但这肯定要等到 7 月底之后。

最后,在我们拥有一个完整的、优化良好的 Perl 6 实现之前,没有人能真正说出它的速度有多快,但我确实希望它比现在好得多。

顺便说一句,您引用的案例[+] 1..$big_number可以在 O(1) 中运行,因为1..$big_number返回一个范围,这是自省的。因此,您可以对案例使用求和公式[+] Range。同样,这是可以做的事情,但还没有做。

于 2010-06-28T21:17:26.207 回答
21

关于缺乏优化,您必须了解的另一件事是它是复合的。Rakudo 的很大一部分是用Perl 6编写的。因此,例如,[+]运算符由方法Any.reduce(使用$expressionset to调用&infix:<+>)实现,该方法具有内部循环

for @.list {
    @args.push($_);
    if (@args == $arity) {
        my $res = $expression.(@args[0], @args[1]);
        @args = ($res);
    }
}

换句话说,reduce 的纯 perl 实现,它本身由 Rakudo 运行。因此,不仅您可以看到的代码没有得到优化,而且您没有看到使您的代码运行的代码也没有得到优化。甚至+操作符的实例实际上也是方法调用,因为虽然+操作符 onNum是由 Parrot 实现的,但 Rakudo 中还没有识别出你有两个Nums 并优化方法调用,所以在 Rakudo 找到之前有一个完整的动态调度multi sub infix:<+>(Num $a, Num $b)并意识到它真正在做的只是一个“添加”操作码。这是比 Perl 5 慢 100-1000 倍的合理借口 :)

2010 年 8 月 23 日更新

来自 Jonathan Worthington 的更多信息,关于 Perl 6 对象模型(或至少 Rakudo 的概念)需要发生的各种变化,以使事情变得快速,同时保持 Perl 6 的“一切都是方法调用”的性质。

2019 年 1 月 10 日更新

因为我可以看到这仍然受到关注……多年来,Rakudo/MoarVM 已经获得了 JIT、内联、动态专业化以及许多人优化系统每个部分的大量工作。结果是这些方法调用中的大多数都可以“编译出来”并且运行时成本几乎为零。Perl 6 在许多基准测试中的得分比 2010 年快了数百或数千倍,在某些情况下它比 Perl 5 还要快。

在问题开始的 sum-to-100,000 问题的情况下,Rakudo 2018.06 仍然比 perl 5.26.2 慢一点:

$ time perl -e 'use List::Util 'sum'; print sum(1 .. 100000), "\n";' >/dev/null

real    0m0.023s
user    0m0.015s
sys     0m0.008s

$ time perl6 -e 'say [+] 1 .. 100000;' >/dev/null

real    0m0.089s
user    0m0.107s
sys     0m0.022s

但是,如果我们通过运行代码 10,000 次来摊销启动成本,我们会看到一个不同的故事:

$ time perl -e 'use List::Util 'sum'; for (1 .. 10000) { print sum(1 .. 100000), "\n"; }' > /dev/null

real    0m16.320s
user    0m16.317s
sys     0m0.004s

$ time perl6 -e 'for 1 .. 10000 { say [+] 1 .. 100000; }' >/dev/null

real    0m0.214s
user    0m0.245s
sys     0m0.021s

perl6 在启动和编译时比 perl5 多使用几百毫秒,但随后它计算出如何以大约 70 倍的速度进行实际求和。

于 2010-06-29T04:11:09.403 回答
6

这当然不是因为一切都是对象,因为在许多其他语言(如 Ruby)中也是如此。Perl 6 没有理由比 Perl 5 或 Ruby 等其他语言慢很多,但事实是 Rakudo 不如 perl 或 CRuby 成熟。还没有太多的速度优化。

于 2010-06-28T20:34:50.790 回答
6

考虑到现在您的测试用例已优化为几乎立即返回的O(1)算法,并且看起来几乎每周都有几次优化;
我期望在所有方面都有相当大的性能改进。

$ perl6 -e 'say [+] 1..10**1000; say now - INIT now'
5000000000000000000000000000000000000000000000 ...
0.007447

即使这不是范围的特殊情况,它仍然比以前快很多。
它现在可以在不到五分之一秒的时间内完成您的测试计算。

$ perl6 -e 'say [+] (1..100000).list; say now - INIT now'
5000050000
0.13052975
于 2015-11-18T19:51:17.673 回答
4

我在 2008 年 12 月将这些提交给了Fefe 的语言比赛wp.pugs.pl。是 Perl 5 示例的直译,wp.rakudo.pl更简洁。我有两个程序,因为这两个程序实现了规范的不同子集。同时构建信息已过时。消息来源:

#!/usr/bin/env pugs
# Pugs: <http://pugs.blogs.com/> <http://pugscode.org/>
# prerequisite: ghc-6.8.x, not 6.10.x
# svn co http://svn.pugscode.org/pugs/
# perl Makefile.PL
# make
# if build stops because of haskeline, do:
#   $HOME/.cabal/bin/cabal update ; $HOME/.cabal/bin/cabal install haskeline

# learn more: <http://jnthn.net/papers/2008-tcpw-perl64danoob-slides.pdf>

my %words;

for =<> {
    for .split {
        %words{$_}++
    }
}

for (sort { %words{$^b} <=> %words{$^a} }, %words.keys) {
    say "$_ %words{$_}"
}

#!/usr/bin/env perl6
# Rakudo: <http://rakudo.org/> <http://www.parrot.org/download>
# svn co http://svn.perl.org/parrot/trunk parrot
# perl Configure.pl
# make perl6

# Solution contributed by Frank W. & Moritz Lenz
# <http://use.perl.org/~fw/journal/38055>
# learn more: <http://jnthn.net/papers/2008-tcpw-perl64danoob-slides.pdf>

my %words;

$*IN.lines.split(/\s+/).map: { %words{$_}++ };

for %words.pairs.sort: { $^b.value <=> $^a.value } -> $pair {
    say $pair
}

这些是 2008 年的结果:

$ time ./wp.pugs.pl < /usr/src/linux/COPYING > foo

real    0m2.529s
user    0m2.464s
sys     0m0.064s

$ time ./wp.rakudo.pl < /usr/src/linux/COPYING > foo

real    0m32.544s
user    0m1.920s
sys     0m0.248s

今天:

$ time ./wp.pugs.pl < /usr/src/linux/COPYING > foo

real    0m5.105s
user    0m4.898s
sys     0m0.096s

$ time ./wp.rakudo.pl < /usr/src/linux/COPYING > foo
Divide by zero
current instr.: '' pc -1 ((unknown file):-1)
Segmentation fault

real    0m3.236s
user    0m0.447s
sys     0m0.080s

后期补充:当我尝试使用 Rakudo 运行脚本时,为什么会出现“除以零”错误?. Rakudo 程序效率低下,请参阅下面的评论http://justrakudoit.wordpress.com/2010/06/30/rakudo-and-speed/

于 2010-06-28T21:51:00.250 回答