如何在Perl 6中高效生成素数列表?

时间:2017-04-29 23:53:37

标签: primes perl6

在Perl 6中生成素数列表非常容易:

my @primes = (^∞).grep: *.is-prime;

如果您需要相对较少的素数,这种方法效果很好,但对于大数字而言效率非常低,因为每个数字都是独立检查的。

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

3 个答案:

答案 0 :(得分:5)

如果您使用--profile运行程序,您会看到超过99%的时间花在Int.is-prime上。由于这实际上只是nqp::isprime_I()的包装,我试图在没有包装器的情况下运行类似的代码。但这并没有明显改变。因此,工作首当其冲的是nqp::isprime_I()

因此,您真正拥有的唯一选择是并行搜索素数。在(更近的)未来,hyper将成为你的朋友。但目前处于“最初的初始实施”阶段,正在讨论更强大的实施:https://gist.github.com/jnthn/6a80a9712fb38b32537f9f0e46fca6d7

在此之前,如果您想更快地运行,则必须手动分解要检查准备情况的值范围并在start块内运行它们,并从结果中收集结果Promise

答案 1 :(得分:4)

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

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

数学:: Primesieve

答案 2 :(得分:2)

这不是我的问题的答案,但我通过几种方法来获得素数列表。

结论:

  1. .is-prime确实太慢了(虽然@DanaJ&#39的分支希望能有所改善)。
  2. Perl 6代码中的筛子并不像我担心的那么慢,只要你稍微优化一下(即使代码不那么漂亮Perl6ish)。
  3. 原生代码(通过Perl 5模块)仍然更快。
  4. 作弊最快。
  5. 修改:添加了Curt Tilmes' Primesieve模块。哇,它快!它胜过作弊(即从文件中读取素数)! 那么,这可能是因为Primesieve不支持生成器/迭代器(还是?),所以我只是立即返回整个列表,而所有其他版本都使用生成器/惰性列表。

    编辑:添加了“更优化”的筛子,基于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