gpt4 book ai didi

performance - Perl6 : What is the best way for dealing with very big files?

转载 作者:行者123 更新时间:2023-12-03 20:55:20 25 4
gpt4 key购买 nike

上周我决定尝试 Perl6 并开始重新实现我的一个程序。
我不得不说,Perl6 非常容易进行对象编程,这是 Perl5 中对我来说非常痛苦的一个方面。

我的程序必须读取和存储大文件,例如全基因组(最多 3 Gb 及更多,请参见下面的示例 1)或表格数据。

代码的第一个版本是通过逐行迭代(“genome.fa”.IO.lines)以 Perl5 方式制作的。对于正确的执行时间,它非常缓慢且无法使用。

my class fasta {
has Str $.file is required;
has %!seq;

submethod TWEAK() {
my $id;
my $s;

for $!file.IO.lines -> $line {
if $line ~~ /^\>/ {
say $id;
if $id.defined {
%!seq{$id} = sequence.new(id => $id, seq => $s);
}
my $l = $line;
$l ~~ s:g/^\>//;
$id = $l;
$s = "";
}
else {
$s ~= $line;
}
}
%!seq{$id} = sequence.new(id => $id, seq => $s);
}
}


sub MAIN()
{
my $f = fasta.new(file => "genome.fa");
}

因此,在进行了一些 RTFM 之后,我更改了文件上的一个 slurp,在我用 for 循环解析的\n 上进行了拆分。通过这种方式,我设法在 2 分钟内加载了数据。好多了,但还不够。通过作弊,我的意思是通过删除最多的\n(示例 2),我将执行时间减少到 30 秒。相当不错,但并不完全满意,这种 fasta 格式不是最常用的。
my class fasta {
has Str $.file is required;
has %!seq;

submethod TWEAK() {
my $id;
my $s;

say "Slurping ...";
my $f = $!file.IO.slurp;

say "Spliting file ...";
my @lines = $f.split(/\n/);

say "Parsing lines ...";
for @lines -> $line {
if $line !~~ /^\>/ {
$s ~= $line;
}
else {
say $id;
if $id.defined {
%!seq{$id} = seq.new(id => $id, seq => $s);
}
$id = $line;
$id ~~ s:g/^\>//;
$s = "";
}
}
%!seq{$id} = seq.new(id => $id, seq => $s);
}
}

sub MAIN()
{
my $f = fasta.new(file => "genome.fa");
}

所以再次 RTFM,我发现了语法的魔力。无论使用哪种 fasta 格式,新版本和 45 秒的执行时间。不是最快的方式,但更优雅和稳定。
my grammar fastaGrammar {
token TOP { <fasta>+ }

token fasta {<.ws><header><seq> }
token header { <sup><id>\n }
token sup { '>' }
token id { <[\d\w]>+ }
token seq { [<[ACGTNacgtn]>+\n]+ }

}

my class fastaActions {
method TOP ($/){
my @seqArray;

for $<fasta> -> $f {
@seqArray.push: seq.new(id => $f.<header><id>.made, seq => $f<seq>.made);
}
make @seqArray;
}

method fasta ($/) { make ~$/; }
method id ($/) { make ~$/; }
method seq ($/) { make $/.subst("\n", "", :g); }

}

my class fasta {
has Str $.file is required;
has %seq;

submethod TWEAK() {

say "=> Slurping ...";
my $f = $!file.IO.slurp;

say "=> Grammaring ...";
my @seqArray = fastaGrammar.parse($f, actions => fastaActions).made;

say "=> Storing data ...";
for @seqArray -> $s {
%!seq{$s.id} = $s;
}
}
}

sub MAIN()
{
my $f = fasta.new(file => "genome.fa");
}

我认为我找到了处理这些大文件的好方法,但性能仍然低于 Perl5。

作为 Perl6 的新手,我很想知道是否有更好的方法来处理大数据,或者是否由于 Perl6 实现而存在一些限制?

作为 Perl6 的新手,我会问两个问题:
  • 是否还有其他我不知道或不知道的 Perl6 机制
    记录,用于存储来自文件的大量数据(如我的基因组)?
  • 我是否达到了当前版本的最大性能
    Perl6 ?

  • 谢谢阅读 !

    Fasta 示例 1:
    >2L
    CGACAATGCACGACAGAGGAAGCAGAACAGATATTTAGATTGCCTCTCATTTTCTCTCCCATATTATAGGGAGAAATATG
    ATCGCGTATGCGAGAGTAGTGCCAACATATTGTGCTCTTTGATTTTTTGGCAACCCAAAATGGTGGCGGATGAACGAGAT
    ...
    >3R
    CGACAATGCACGACAGAGGAAGCAGAACAGATATTTAGATTGCCTCTCATTTTCTCTCCCATATTATAGGGAGAAATATG
    ATCGCGTATGCGAGAGTAGTGCCAACATATTGTGCTCTTTGATTTTTTGGCAACCCAAAATGGTGGCGGATGAACGAGAT
    ...

    Fasta 示例 2:
    >2L
    GACAATGCACGACAGAGGAAGCAGAACAGATATTTAGATTGCCTCTCAT...
    >3R
    TAGGGAGAAATATGATCGCGTATGCGAGAGTAGTGCCAACATATTGTGCT...

    编辑
    我应用了@Christoph 和 @timotimo 的建议并使用代码进行测试:
    my class fasta {
    has Str $.file is required;
    has %!seq;

    submethod TWEAK() {
    say "=> Slurping / Parsing / Storing ...";
    %!seq = slurp($!file, :enc<latin1>).split('>').skip(1).map: {
    .head => seq.new(id => .head, seq => .skip(1).join) given .split("\n").cache;
    }
    }
    }


    sub MAIN()
    {
    my $f = fasta.new(file => "genome.fa");
    }

    程序在2.7s内完成,太棒了!
    我也在小麦基因组 (10 Gb) 上尝试了这个代码。它在 35.2 秒内完成。
    Perl6 终于没那么慢了!

    非常感谢您的帮助!

    最佳答案

    一个简单的改进是使用固定宽度的编码,例如 latin1加速字符解码,虽然我不确定这会有多大帮助。

    就 Rakudo 的正则表达式/语法引擎而言,我发现它非常慢,因此可能确实需要采用更底层的方法。

    我没有做任何基准测试,但我首先尝试的是这样的:

    my %seqs = slurp('genome.fa', :enc<latin1>).split('>')[1..*].map: {
    .[0] => .[1..*].join given .split("\n");
    }

    由于 Perl6 标准库是在 Perl6 本身中实现的,因此有时可以通过避免它来提高性能,以命令式风格编写代码,例如:
    my %seqs;
    my $data = slurp('genome.fa', :enc<latin1>);
    my $pos = 0;
    loop {
    $pos = $data.index('>', $pos) // last;

    my $ks = $pos + 1;
    my $ke = $data.index("\n", $ks);

    my $ss = $ke + 1;
    my $se = $data.index('>', $ss) // $data.chars;

    my @lines;

    $pos = $ss;
    while $pos < $se {
    my $end = $data.index("\n", $pos);
    @lines.push($data.substr($pos..^$end));
    $pos = $end + 1
    }

    %seqs{$data.substr($ks..^$ke)} = @lines.join;
    }

    但是,如果使用的标准库的部分已经看到了一些性能工作,这实际上可能会使事情变得更糟。在这种情况下,下一步是添加低级类型注释,例如 strint并替换对例程的调用,例如 .indexNQP builtinsnqp::index .

    如果这仍然太慢,那你就不走运了,需要切换语言,例如使用 Inline::Perl5 调用 Perl5或 C 使用 NativeCall .

    请注意,@timotimo 做了一些性能测量并写了 an article关于它。

    如果我的简短版本是基准,那么命令式版本将性能提高了 2.4 倍。

    他实际上设法通过将简短版本重写为 3 倍改进
    my %seqs = slurp('genome.fa', :enc<latin-1>).split('>').skip(1).map: {
    .head => .skip(1).join given .split("\n").cache;
    }

    最后,使用 NQP 内置函数重写命令式版本将速度提高了 17 倍,但考虑到潜在的可移植性问题,通常不鼓励编写此类代码,但如果您确实需要该级别的性能,现在可能有必要:
    use nqp;

    my Mu $seqs := nqp::hash();
    my str $data = slurp('genome.fa', :enc<latin1>);
    my int $pos = 0;

    my str @lines;

    loop {
    $pos = nqp::index($data, '>', $pos);

    last if $pos < 0;

    my int $ks = $pos + 1;
    my int $ke = nqp::index($data, "\n", $ks);

    my int $ss = $ke + 1;
    my int $se = nqp::index($data ,'>', $ss);

    if $se < 0 {
    $se = nqp::chars($data);
    }

    $pos = $ss;
    my int $end;

    while $pos < $se {
    $end = nqp::index($data, "\n", $pos);
    nqp::push_s(@lines, nqp::substr($data, $pos, $end - $pos));
    $pos = $end + 1
    }

    nqp::bindkey($seqs, nqp::substr($data, $ks, $ke - $ks), nqp::join("", @lines));
    nqp::setelems(@lines, 0);
    }

    关于performance - Perl6 : What is the best way for dealing with very big files?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52005146/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com