【发布时间】:2019-01-30 23:24:12
【问题描述】:
上周我决定尝试 Perl6 并开始重新实现我的一个程序。 我不得不说,Perl6 对对象编程来说太容易了,在 Perl5 中对我来说是一个非常痛苦的方面。
我的程序必须读取和存储大文件,例如全基因组(高达 3 Gb 或更多,参见下面的示例 1)或表格数据。
代码的第一个版本是以 Perl5 的方式通过逐行迭代(“genome.fa”.IO.lines)制作的。对于正确的执行时间,它非常缓慢且不稳定。
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
...
快速示例 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.7秒完成,太棒了! 我还在小麦基因组(10 Gb)上尝试了这个代码。它在 35.2 秒内完成。 Perl6 终于没那么慢了!
非常感谢您的帮助!
【问题讨论】:
-
您已经在尝试几种不同的机制。如果没有更多关于您正在尝试做的事情的详细信息,将很难回答这个问题。
-
限制 Larry on P6 speed in general and grammars in particular 从 11 分 30 秒到 17 分 20 秒。 (我很确定他没有将 Fates 从 STD 移植到 NQP。)请描述参见P6 doc & beta of new profiler 使用 P6+P5? 1) P6在 P5 metacpan.org/pod/Inline::Perl6,2) P5 在 P6 github.com/niner/Inline-Perl5。 改进您的 P5 OO? Stevan 设计了 P6 OO,然后是 Moose 的 P5,现在 Moxie。
-
您的第一个示例可以通过将正则表达式的使用降至零来变得更快;在我的机器上,它从 40 秒下降到 7.6 秒,发生以下变化:
$file.IO.slurp.lines而不是$file.IO.lines,$line.starts-with(">")而不是line ~~ /^>/,$l = $l.substr(1)而不是$l ~~ s:/^>//。另外,从循环中删除“say $id”。如果你真的需要输出,可以在 TWEAK 的末尾或在单独的方法中使用put %!seq.keys.join("\n")。 -
对于任何想要尝试该脚本的人,这里有一个生成一些示例 fasta 数据的单行代码:
my $f = "genome.fa".IO.open(:w); my @ids = ("A".."Z").combinations(20).pick(*); while $f.tell < 300_000_000 { $f.put(">" ~ @ids.shift.join()); $f.put(<A C G T>.roll(80).join()) for ^(2..15).pick } -
我不确定它是属于评论还是属于它自己的答案,但这是我写的一篇关于这个问题的博客文章:“Fast FASTA, please”:wakelift.de/2018/08/31/faster-fasta-please
标签: performance parsing grammar fasta raku