我如何在 Perl 6 中有效地生成素数列表?

How do I efficiently generate a list of primes in Perl 6?

使用 is-prime:

在 Perl 6 中生成质数列表非常容易
my @primes = (^∞).grep: *.is-prime;

如果您需要的素数数量相对较少,这就足够了,但对于大量素数来说效率很低,因为每个数字都是独立检查的。

有没有办法访问 Perl 6 的内置素数检查逻辑来有效地创建素数列表? 否则我需要自己做一个筛子。很简单,但我担心高级 Perl 6 代码中的筛子几乎和我开始使用的代码一样低效。

如果您 运行 您的程序使用 --profile,您会发现超过 99% 的时间花在 Int.is-prime 上。由于这实际上只是 nqp::isprime_I() 的包装器,我尝试 运行 没有包装器的类似代码。但这并没有明显改变任何东西。所以 b运行t 的工作正在 nqp::isprime_I().

中完成

所以你真正拥有的唯一选择是并行化你对素数的搜索。在(较近的)未来,hyper 将成为您的朋友。但这目前处于 "initial naive implementation" 阶段,正在讨论更强大的实施:https://gist.github.com/jnthn/6a80a9712fb38b32537f9f0e46fca6d7

在那之前,如果你想 运行 事情更快,你将不得不手动分解你想要检查素数的值范围,并 运行 它们在 start 块,并从结果 Promise.

中收集结果

这并不是我问题的真正答案,但我对几种获取素数列表的方法进行了基准测试。

结论:

  1. .is-prime 确实太慢了(尽管@DanaJ 的分支希望能稍微改进一下)。
  2. Perl 6 代码中的筛子并不像我担心的那么慢,只要你稍微优化一下(即让代码不那么漂亮 Perl6ish)。
  3. 本机代码(通过 Perl 5 模块)仍然更快。
  4. 作弊是最快的。

编辑: 添加了 Curt Tilmes 的 Primesieve 模块。哇,好快!它胜过作弊(即从文件中读取质数)! 好吧,这可能是因为 Primesieve 不支持 generator/iterator(还?),所以我只是一次返​​回整个列表,而所有其他版本都使用生成器/惰性列表。

编辑: 根据 Timbus 的评论添加了“更加优化”的筛选器。这个性能还不错,但是代码几乎跟不上...

编辑: 添加了一个更好的纯 Perl6 版本,具有灵活的筛子大小。我从奇数 1..99 的(预初始化)筛子开始,并根据需要将筛子大小加倍。连同一些进一步的优化(例如,当扩展筛子时,我们只需要检查素数达到√(筛子大小))这是迄今为止最快的纯 Perl 6 版本。更多优势:您不必事先知道限制;如果您确实需要上限,它会更快地给出小素数。

编辑: Math::Primesieve 现在支持迭代器,因此请将其包含在脚本中。

#!/usr/bin/env perl6

use v6.c;

# The easy but slow way
sub primes-is-prime
{
    (^∞).grep: *.is-prime;
}

# Use a sieve (simple Perl 6 style)
sub primes-sieve(Int $max)
{
    my @sieve;
    lazy gather for 2..$max -> $p {
        next if @sieve[$p];
        take $p;
        for 2*$p, 3*$p ... $max -> $n {
            @sieve[$n] = True;
        }
    }
}

# Use a sieve (optimized)
sub primes-sieve2(Int $max)
{
    my int @sieve;
    lazy gather {
        take 2;
        loop (my int $p = 3; $p ≤ $max; $p += 2) {
            next if @sieve[$p];
            take $p;
            loop (my int $n = 3*$p; $n ≤ $max; $n += 2*$p) {
                @sieve[$n] = 1;
            }
        }
    }
}

# Use a sieve (even more optimized)
sub primes-sieve3(Int $max)
{
    my int @sieve;
    my $max2 = ($max-1) div 2;
    lazy gather {
        take 2;
        for 1 .. $max2 -> int $i {
            next if @sieve[$i];
            take 2*$i + 1;
            my $max3 = ($max2 - $i) div (2*$i + 1);
            for 1 .. $max3 -> int $j {
                @sieve[(2*$j + 1)*$i + $j] = 1;
            }
        }
    }
}

# Use a flexible sieve size (and further optimized)
sub primes-sieve4
{
    # Pre-initialize our sieve with the odd numbers from 1 to 99
    my $max = 100;
    my int @sieve = 1,0,0,0,1,0,0,1,0,0,1,0,1,1,0,0,1,1,0,1,0,0,1,0,1,
                    1,0,1,1,0,0,1,1,0,1,0,0,1,1,0,1,0,1,1,0,1,1,1,0,1;

    lazy gather {
        # Don't forget our single even prime number
        take 2;

        my int $min-i = 1;
        loop {
            # Take all primes in the new part of the sieve 
            my int $max-i = ($max-1) div 2;
            for $min-i .. $max-i -> int $i {
                take 2*$i + 1 unless @sieve[$i];
            }

            # Extend sieve by factor 2
            # We must check the primes from 3 to √(2*max) in the sieve
            # for max to 2*max
            for 1 .. ((2*$max).sqrt.floor-1) div 2 -> int $i {
                next if @sieve[$i];
                my int $p = 2*$i + 1;
                my int $min-j = max(($max-i - $i) div $p, $i);
                my int $max-j = (2*$max-i + 1 - $i) div $p;
                for $min-j .. $max-j -> int $j {
                    @sieve[$i + $p*$j] = 1;
                }
            }

            # Double the sieve size, and start the next iteration
            # in the second half of the sieve
            $max *= 2;
            $min-i = $max-i+1;
        }
    }
}

# Use a Perl 5 module
sub primes-perl5
{
    use Math::Prime::Util:from<Perl5> <prime_iterator>;

    my $it = prime_iterator;
    lazy gather {
        loop {
            take $it.();
        }
    }
}

# Use Primesieve module
sub primes-primesieve($max)
{
    # See:
    #  - http://primesieve.org/
    #  - https://github.com/CurtTilmes/perl6-primesieve
    use Math::Primesieve;

    # No iterator support (yet?), so just return the whole list
    return Math::Primesieve.new.primes($max);
}

# Use Primesieve module (iterator)
sub primes-primesieve-iterator
{
    # See:
    #  - http://primesieve.org/
    #  - https://github.com/CurtTilmes/perl6-primesieve
    use Math::Primesieve;
    my $iterator = Math::Primesieve::iterator.new;
    lazy gather {
        loop {
            take $iterator.next;
        }
    }
}

# Cheat
# Source: https://primes.utm.edu/lists/small/millions/ - first million
# (Unzip and remove the first few lines from the file.)
sub primes-cheat
{
    lazy $*PROGRAM.sibling('primes1.txt').words.map(+*);
}

sub timer(&code)
{
    my $start = now;
    &code();
    my $elapsed = now - $start;
    say "Elapsed: $elapsed.fmt('%.3f')s";
}

sub MAIN
{
    #my $nth = 1_000;
    #my $max = 8_000;

    #my $nth = 10_000;
    #my $max = 105_000;

    my $nth = 50_000;
    my $max = 612_000;

    timer {
        my @primes = primes-is-prime;
        say "Using .is-prime: @primes[$nth]";
    }
    timer {
        my @primes = primes-sieve($max);
        say "Using a sieve (simple Perl 6 style): @primes[$nth]";
    }
    timer {
        my @primes = primes-sieve2($max);
        say "Using a sieve (optimized): @primes[$nth]";
    }
    timer {
        my @primes = primes-sieve3($max);
        say "Using a sieve (even more optimized): @primes[$nth]";
    }
    timer {
        my @primes = primes-sieve4;
        say "Using a flexible sieve size (further optimized): @primes[$nth]";
    }
    timer {
        my @primes = primes-perl5;
        say "Using a Perl 5 module: @primes[$nth]";
    }
    timer {
        my @primes = primes-primesieve($max);
        say "Using Primesieve module: @primes[$nth]";
    }
    timer {
        my @primes = primes-primesieve-iterator;
        say "Using Primesieve module (iterator): @primes[$nth]";
    }
    timer {
        my @primes = primes-cheat;
        say "Cheating: @primes[$nth]";
    }
}

# 4 year old Linux server, running Rakudo Star 2017.04:
#
# Using .is-prime: 611957
# Elapsed: 216.134s
# Using a sieve (simple Perl 6 style): 611957
# Elapsed: 124.087s
# Using a sieve (optimized): 611957
# Elapsed: 41.129s
# Using a sieve (even more optimized): 611957
# Elapsed: 7.285s
# Using a flexible sieve size (further optimized): 611957
# Elapsed: 3.897s
# Using a Perl 5 module: 611957
# Elapsed: 10.031s
# Using Primesieve module: 611957
# Elapsed: 0.312s
# Using Primesieve module (iterator): 611957
# Elapsed: 1.460s
# Cheating: 611957
# Elapsed: 2.017s

我为 primesieve 编写了一些 Perl 6 绑定:

https://github.com/CurtTilmes/perl6-primesieve

数学::初筛