【问题标题】:Perl6 vs Perl5 benchmarking using prime numbers使用素数的 Perl6 与 Perl5 基准测试
【发布时间】:2018-09-11 11:09:53
【问题描述】:

这是从HN thread 转移到这里的讨论,关于使用实现Sieve of Sundaram 以查找素数的算法对 Perl6 与 Perl5 与其他语言进行基准测试。

这是原始线程中的原始代码:

perl50m0.156s

perl60m6.615s

与 Perl5 实现相比,问题在于 Perl6 版本需要很长时间才能找到素数。部分原因是使用 float 作为输入,但仍然太慢了。

目标不一定是优化算法,而是确定 Perl6 与其他语言相比为何如此缓慢。

【问题讨论】:

    标签: benchmarking primes raku perl


    【解决方案1】:

    事实证明,即使素数是整数,在您的 Perl 6 版本中,每个计算都是使用浮点完成的。这是由对子程序的调用引起的。如果你愿意:

    sieve_sundaram(1000)
    

    代替:

    sieve_sundaram(1e3)
    

    然后它突然变得快 4 倍。在 Perl 5 中,您永远不知道您正在处理的值是什么。在 Perl 6 中,如果你告诉它使用浮点数,它会感染所有计算以在之后使用浮点数。 1e3 是一个浮点值。 1000 在 Perl 6 中是一个整数。

    此外,您似乎有一个次优算法:第二个 foreach 不需要从 1..$n 出发,而是可以从 $i..$n 出发。这将 Perl 5 版本代码的运行时间缩短到了 89 毫秒。

    由于您的程序在 Perl 5 版本中没有使用 BigInt,它基本上使用的是原生整数。在 Perl 6 中,所有整数计算始终是 BigInts,除非您将它们标记为原生。如果我为此调整您的 Perl 6 版本,则此版本的运行时间会从 4671 毫秒降至 414 毫秒:

    sub sieve_sundaram(int $n) {
        my %a;
        my int @s = 2;
        my int $m = $n div 2 - 1;
        for 1..$n -> int $i {
            for $i..$n -> int $j {
                my int $p = $i + $j + 2 * $i * $j;
                if $p < $m {
                    %a{$p} = True;
                }
            }
        }
        for 1..$m -> int $k {
            if ! %a{$k} {
                my int $q = 2 * $k + 1;
                @s.push($q);
            }
        }
    
        return @s;
    }
    
    sieve_sundaram(1000);
    

    因此,比以前快了大约 11 倍。并且比 Perl 5 版本慢不到 5 倍。

    在 Perl 6 中获取素数的最惯用的版本是:

    (1..1000).grep( *.is-prime )
    

    这对我来说是在原始 Perl 5 算法的噪音范围内执行的。对于多 CPU 机器上较大的值,我会这样写:

    (1..2500).hyper.grep( *.is-prime )
    

    大约 2500 比 hyper 它更快,因此工作会自动分布在多个 CPU 上。

    【讨论】:

    • RT 130982 的另一个插件。如果您将所有范围循环(如 ...for 1..$n -&gt; int $i)转换为loop (my int $i = 1; $i &lt;= $n; $i++),那么我的性能几乎再次翻倍。
    • 如果没有指定类型提示,为什么它使用 BigInt 执行所有计算,而不是像 Ruby 那样在 int 溢出时从 int 升级到 Bigint?
    • @petre Perl 6 中的 Int 是 Int 对象类型的一个实例。事实证明,混合对象类型 Int 和原生类型 int 比在任何地方都使用 Int 慢,因为很多东西会将 int 自动装箱为 Int。
    • 在对 perls 和 ruby​​ 进行 i..n 优化后,以及 perl6 上的类型提示后,使用 same algorithm 发现 ruby​​ 比 perl5 快 145%,比 perl6 快 347%高达 10k。
    • 只是想提一下,我使用 Mr_ron 提到的 C 风格循环重写了上面的代码,即使代码非常难看,它也几乎和 perl5 版本一样快,13.55s perl6 vs 12.75s perl5。同样$m = $n div 2 - 1 将数字向下舍入,这可能会截断系列的结尾,所以如果你把它跑到一个素数,你会错过它。它应该是$m = ($n/2).round。此外,if $p &lt; $m 条件将错误地将最后一个数字报告为素数,因为它没有包含在非素数哈希中。它应该是$p &lt;= $m
    【解决方案2】:

    我不认为我可以对 Liz 所说的添加更多内容。除了:

    "但是为了对多种语言进行基准测试,我需要运行相同的 无处不在的东西”

    ...其中“相同”在英语中被定义为看起来相似的语法,对于完全不同的编程语言之间的等效性来说,这是一个非常差的标准。 Perl 6 与 Perl 5 具有非常相似甚至相同的语法,但在下面执行非常不同的语义。整个语言已针对正确性进行了调整,默认语法很容易达到,而不是最佳行为。另一个很好的例子是 Perl 6 字符串,它们非常慢,它们总是完全规范化的 unicode 而不是纯字节字符串。对它们的所有操作都考虑了字形的 unicode 概念,而不是字节和字节偏移量。太棒了!但与 C/Perl 5 字符串更等效的类型可能是 Buf,遗憾的是它没有像运算符/方法那样多的字符串,而只是一个字节块。

    【讨论】:

      猜你喜欢
      • 2019-06-26
      • 1970-01-01
      • 1970-01-01
      • 2010-11-14
      • 1970-01-01
      • 1970-01-01
      • 2016-02-18
      • 1970-01-01
      • 2014-09-16
      相关资源
      最近更新 更多