Perl6: ¿Cuál es la mejor manera de manejar archivos muy grandes?

La semana pasada decidí probar Perl6 y comencé a reimplementar uno de mi programa. Debo decir que Perl6 es muy fácil para la programación de objetos, un aspecto muy doloroso para mí en Perl5.

Mi programa tiene que leer y almacenar archivos grandes, como genomas completos (hasta 3 Gb y más, ver ejemplo 1 a continuación) o tabular datos.

La primera versión del código se hizo en la forma Perl5 iterando línea por línea ("genome.fa" .IO.lines). Fue muy lento e insaciable para un tiempo de ejecución correcto.

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");
}

Así que después de un poco de RTFM, cambié por un sorbo en el archivo, una división en el \ n que analicé con un bucle for. De esta manera logré cargar los datos en 2 minutos. Mucho mejor pero no suficiente. Al hacer trampa, quiero decir que al eliminar un máximo de \ n (Ejemplo 2), disminuí el tiempo de ejecución a 30 segundos. Bastante bueno, pero no totalmente satisfecho, por este formato fasta no es el más utilizado.

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");
}

Entonces RTFM nuevamente y descubrí la magia de la gramática. Entonces, nueva versión y un tiempo de ejecución de 45 segundos, independientemente del formato de fasta utilizado. No es la forma más rápida pero más elegante y estable.

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 
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");
}
lt;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"); }

Creo que encontré una buena solución para manejar este tipo de archivos grandes, pero las actuaciones aún están por debajo de las de Perl5.

Como novato en Perl6, ¿me interesaría saber si hay mejores maneras de lidiar con big data o si hay alguna limitación debido a la implementación de Perl6?

Como novato en Perl6, haría dos preguntas:

¿Hay otros mecanismos de Perl6 que aún no conozco, o que aún no he documentado, para almacenar grandes datos de un archivo (como mis genomas)? ¿Alcancé el máximo rendimiento para la versión actual de Perl6?

Gracias por leer

Fasta Ejemplo 1:

>2L
CGACAATGCACGACAGAGGAAGCAGAACAGATATTTAGATTGCCTCTCATTTTCTCTCCCATATTATAGGGAGAAATATG
ATCGCGTATGCGAGAGTAGTGCCAACATATTGTGCTCTTTGATTTTTTGGCAACCCAAAATGGTGGCGGATGAACGAGAT
...
>3R
CGACAATGCACGACAGAGGAAGCAGAACAGATATTTAGATTGCCTCTCATTTTCTCTCCCATATTATAGGGAGAAATATG
ATCGCGTATGCGAGAGTAGTGCCAACATATTGTGCTCTTTGATTTTTTGGCAACCCAAAATGGTGGCGGATGAACGAGAT
...

Fasta ejemplo 2:

>2L
GACAATGCACGACAGAGGAAGCAGAACAGATATTTAGATTGCCTCTCAT...            
>3R
TAGGGAGAAATATGATCGCGTATGCGAGAGTAGTGCCAACATATTGTGCT...

EDITA Apliqué los avisos de @Christoph y @timotimo y probé con el código:

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");
}

El programa terminó en 2.7s, ¡lo cual es genial! También probé este código en el genoma del trigo (10 Gb). Terminó en 35.2s. ¡Perl6 no es tan lento finalmente!

¡Gracias por la ayuda!

Respuestas a la pregunta(1)

Su respuesta a la pregunta