为什么这个记忆化的 Euler14 实现在 Raku 中比 Python 慢得多?

gro*_*ber 10 python memoization collatz raku

我最近摆弄问题14Euler project:该范围内数1..1_000_000的最长的在Collat​​z序列

我知道必须记忆以获得合理时间的问题,并且以下Python代码使用该技术相对快速地返回答案(记忆到字典):

#!/usr/bin/env python

L = 1_000_000
cllens={1:1}

cltz = lambda n: 3*n + 1 if n%2 else n//2

def cllen(n):
    if n not in cllens: cllens[n] = cllen(cltz(n)) + 1
    return cllens[n]

maxn=1
for i in range(1,L+1):
    ln=cllen(i)
    if (ln > cllens[maxn]): maxn=i

print(maxn)
Run Code Online (Sandbox Code Playgroud)

(从这里改编;我更喜欢这个不使用的版本max,因为我可能想摆弄它以返回最长的 10 个序列等)。

我试图将其翻译为尽可能Raku保持语义上的接近:

#!/usr/bin/env perl6
use v6;

my $L=1_000_000;
my %cllens = (1 => 1);

sub cltz($n) { ($n %% 2) ?? ($n div 2) !! (3*$n+1) }

sub cllen($n) {
    (! %cllens{$n}) && (%cllens{$n} = 1+cllen($n.&cltz));
    %cllens{$n};
}

my $maxn=1;
for (1..$L) {
    my $ln = cllen($_);
    ($ln > %cllens{$maxn}) && ($maxn = $_)
}
say $maxn
Run Code Online (Sandbox Code Playgroud)

以下是我一直在运行这些的次数:

$ time <python script>
837799

real    0m1.244s
user    0m1.179s
sys     0m0.064s
Run Code Online (Sandbox Code Playgroud)

另一方面,在Raku

$ time <raku script>
837799

real    0m21.828s
user    0m21.677s
sys     0m0.228s
Run Code Online (Sandbox Code Playgroud)

问题)

我是否在两者之间翻译有误,或者差异是启动 VM 等不可调和的问题吗?

是否有巧妙的调整/习惯用法可以应用于Raku代码以大大加快速度?

在旁边

自然,这不是关于这个特定Euler project问题的。我对是否有任何适合Raku我不知道的神奇加速奥秘更感兴趣。

Bra*_*ert 7

我认为大部分额外时间是因为 Raku 进行了类型检查,并且它们不会被运行时类型专门程序删除。或者,如果它们被移除,则是在很长一段时间之后。

通常优化 Raku 代码的方法是首先使用分析器运行它:

$ raku --profile test.raku
Run Code Online (Sandbox Code Playgroud)

当然,此代码会因段错误而失败,因此我们无法使用它。

我的猜测是大部分时间都与使用哈希有关。

如果实现了,对键和值使用本机整数可能会有所帮助:

 my int %cllens{int} = (1 => 1);
Run Code Online (Sandbox Code Playgroud)

然后将函数声明为使用本机大小的整数可能是一个更大的胜利。
(目前,这充其量只是一个小改进。)

sub cltz ( int $n --> int ) {…}
sub cllen( int $n --> int ) {…}
for (1..$L) -> int $_ {…}
Run Code Online (Sandbox Code Playgroud)

当然,就像我说的未实现本机散列,所以这纯粹是猜测。


您可以尝试使用 Raku 的多进程功能,但共享%cllens变量可能存在问题。


问题也可能是因为递归。(结合上述类型检查。)

如果你重写cllen它使用循环而不是递归,这可能会有所帮助。


注意:
最接近n not in cllens的可能是%cllens{$n}:!exists.
尽管这可能比仅检查值不为零要慢。

也是cellen有点可怕。我会更像这样写:

sub cllen($n) {
    %cllens{$n} //= cllen(cltz($n)) + 1
}
Run Code Online (Sandbox Code Playgroud)

  • 谢谢!回复:你的替代“cllen”:我可能也会写这个!:-) 我经常使用`//=`,但并不追求优雅,尝试了最直接的翻译,然后发布了它。 (2认同)
  • 嗨@grobber。“发布......最直接的翻译”我认为这有帮助。我已经为这个高质量的SO添加了书签。“我可能也会写这个!:-)”我三个。:) 这是我昨晚尝试的第一件事。“不是为了优雅”。我使用 `op=` 的原因之一是美观,但它通常也快得多。它使我的运行时间减少了一半。我选择传递注释,因为这是我唯一的成功,并且比 python 慢 20 倍到 10 倍并不感觉像“习语[复数]我可以应用到 Raku 代码来加速它**显着** ”,也不是“神奇的加速奥秘”。 (2认同)