6

Erlangを使用してfastaシーケンスの平均長を取得しようとしています。fastaファイルは次のようになります

>title1
ATGACTAGCTAGCAGCGATCGACCGTCGTACGC
ATCGATCGCATCGATGCTACGATCGATCATATA
ATGACTAGCTAGCAGCGATCGACCGTCGTACGC
ATCGATCGCATCGATGCTACGATCTCGTACGC
>title2
ATCGATCGCATCGATGCTACGATCTCGTACGC
ATGACTAGCTAGCAGCGATCGACCGTCGTACGC
ATCGATCGCATCGATGCTACGATCGATCATATA
ATGACTAGCTAGCAGCGATCGACCGTCGTACGC
>title3
ATCGATCGCATCGAT(...)

私は次のErlangコードを使用してこの質問に答えようとしました:

-module(golf).
-export([test/0]).

line([],{Sequences,Total}) ->  {Sequences,Total};
line(">" ++ Rest,{Sequences,Total}) -> {Sequences+1,Total};
line(L,{Sequences,Total}) -> {Sequences,Total+string:len(string:strip(L))}.

scanLines(S,Sequences,Total)->
        case io:get_line(S,'') of
            eof -> {Sequences,Total};
            {error,_} ->{Sequences,Total};
            Line -> {S2,T2}=line(Line,{Sequences,Total}), scanLines(S,S2,T2)
        end  .

test()->
    {Sequences,Total}=scanLines(standard_io,0,0),
    io:format("~p\n",[Total/(1.0*Sequences)]),
    halt().

コンパイル/実行

erlc golf.erl
erl -noshell -s golf test < sequence.fasta
563.16

このコードは小さなfastaファイルでは問題なく機能するようですが、大きなファイル(> 100Mo)の解析には数時間かかります。なんで ?私はアーランの初心者です。このコードを改善していただけませんか?

4

5 に答える 5

5

本当に高速なIOが必要な場合は、通常よりも少し注意が必要です。

-module(g).
-export([s/0]).
s()->
  P = open_port({fd, 0, 1}, [in, binary, {line, 256}]),
  r(P, 0, 0),
  halt().
r(P, C, L) ->
  receive
    {P, {data, {eol, <<$>:8, _/binary>>}}} ->
      r(P, C+1, L);
    {P, {data, {eol, Line}}} ->
      r(P, C, L + size(Line));
    {'EXIT', P, normal} ->
      io:format("~p~n",[L/C])
  end.

私が知っているように、これは最速のIOですが、注意してください-noshell -noinput。と同じようにコンパイルしますerlc +native +"{hipe, [o3]}" g.erlが、-smp disable

erl -smp disable -noinput -mode minimal -boot start_clean -s erl_compile compile_cmdline @cwd /home/hynek/Download @option native @option '{hipe, [o3]}' @files g.erl

実行します:

time erl -smp disable -noshell -mode minimal -boot start_clean -noinput -s g s < uniprot_sprot.fasta
352.6697028442464

real    0m3.241s
user    0m3.060s
sys     0m0.124s

-smp enableネイティブではありますが、次のようになります。

$ erlc +native +"{hipe, [o3]}" g.erl
$ time erl -noshell -mode minimal -boot start_clean -noinput -s g s<uniprot_sprot.fasta
352.6697028442464

real    0m5.103s
user    0m4.944s
sys     0m0.112s

バイトコードですが、-smp disable(ほとんどの作業はポートで行われるため、ネイティブとほぼ同等です!):

$ erlc g.erl
$ time erl -smp disable -noshell -mode minimal -boot start_clean -noinput -s g s<uniprot_sprot.fasta
352.6697028442464

real    0m3.565s
user    0m3.436s
sys     0m0.104s

smpを使用した完全性のためのバイトコード:

$ time erl -noshell -mode minimal -boot start_clean -noinput -s g s<uniprot_sprot.fasta 
352.6697028442464

real    0m5.433s
user    0m5.236s
sys     0m0.128s

比較のために、サーノルド バージョンは私に間違った答えを与え、同じハードウェアでより多くを取ります:

$ erl -smp disable -noinput -mode minimal -boot start_clean -s erl_compile compile_cmdline @cwd /home/hynek/Download @option native @option '{hipe, [o3]}' @files golf.erl
./golf.erl:5: Warning: variable 'Rest' is unused
$ time erl -smp disable -noshell -mode minimal -s golf test
359.04679841439776

real    0m17.569s
user    0m16.749s
sys     0m0.664s

編集:私はの特徴を見てきました、uniprot_sprot.fastaそして私は少し驚いています。これは、3824397行および232MBです。これは、-smp disabledバージョンが1秒あたり118万のテキスト行を処理できることを意味します(行指向IOでは71MB /秒)。

于 2010-07-21T22:08:07.517 に答える
3

私もErlangを学んでいます。楽しい質問をありがとう。

文字のリストは非常に遅くなる可能性があるため、Erlang文字列の操作を理解しています。代わりにバイナリを使用できる場合は、パフォーマンスが向上するはずです。バイナリで任意の長さの文字列をどのように使用するかはわかりませんが、整理できれば役立ちます。

また、 ではなくファイルを直接操作することを気にしない場合は、 を使用して作業をstandard_io高速化できますfile:open(..., [raw, read_ahead])rawファイルがローカルノードのファイルシステム上になければならないことを意味し、read_aheadErlang がバッファを使用してファイル IO を実行する必要があることを指定します。(バッファリングの有無にかかわらず、C の stdio 機能を使用することを考えてください。)

read_aheadが最も大きな違いを生むと思いますが、Erlang のすべてに「推測する前のベンチマーク」というフレーズが含まれています。

編集

完全な uniprot_sprot.fasta データセットでfile:open("uniprot_sprot.fasta", [read, read_ahead])getsを使用します。1m31s(平均 359.04679841439776)

とを使用するfile:open(.., [read, read_ahead])file:read_line(S)、 が得られ0m34sます。

とを使用するfile:open(.., [read, read_ahead, raw])file:read_line(S)、 が得られ0m9sます。はい、9秒です。

これが私が今立っている場所です。リストの代わりにバイナリを使用する方法を理解できれば、さらに改善される可能性があります。

-module(golf).
-export([test/0]).

line([],{Sequences,Total}) ->  {Sequences,Total};
line(">" ++ Rest,{Sequences,Total}) -> {Sequences+1,Total};
line(L,{Sequences,Total}) -> {Sequences,Total+string:len(string:strip(L))}.

scanLines(S,Sequences,Total)->
        case file:read_line(S) of
            eof -> {Sequences,Total};
            {error,_} ->{Sequences,Total};
            {ok, Line} -> {S2,T2}=line(Line,{Sequences,Total}), scanLines(S,S2,T2)
        end  .

test()->
    F = file:open("/home/sarnold/tmp/uniprot_sprot.fasta", [read, read_ahead, raw]),
    case F of
    { ok, File } -> 
        {Sequences,Total}=scanLines(File,0,0),
        io:format("~p\n",[Total/(1.0*Sequences)]);
    { error, Reason } ->
            io:format("~s", Reason)
    end,
    halt().
于 2010-07-21T07:04:48.527 に答える
2

ファイルを raw モードで開くことにより、大きなパフォーマンスの問題が解決されたように見えますが、そのコードをさらに最適化する必要がある場合は、さらにいくつかの考えがあります。

fprof を学び、使用します。

string:strip/1主に末尾の改行を削除するために使用しています。erlang の値は不変であるため、最後の文字を削除するためだけに、リストの完全なコピーを作成する必要があります (関連するすべてのメモリ割り当てを含む)。ファイルの形式が適切であることがわかっている場合は、カウントから 1 を引いてください。それ以外の場合は、関連する文字の数をカウントし、無関係なものを無視する長さ関数を作成してみます。

バイナリがリストよりも優れているというアドバイスには警戒していますが、処理が少ないことを考えると、おそらくここではそうです。最初のステップは、バイナリ モードでファイルを開き、 を使用erlang:size/1して長さを見つけることです。

パフォーマンスに (大きな) 影響はありませんが、1.0 による乗算は、除算がTotal/(1.0*Sequences)壊れている言語でのみ必要です。Erlang 除算は正しく機能します。

于 2010-07-21T10:47:24.823 に答える
1

呼び出しstring:len(string:strip(L))は、リストを少なくとも 2 回トラバースします (string:strip の実装については知りません)。代わりに、スペースなしで行の長さをカウントする単純な関数を作成できます。

stripped_len(L) ->
  stripped_len(L, 0).

stripped_len([$ |L], Len) ->
  stripped_len(L, Len);

stripped_len([_C|L], Len) ->
  stripped_len(L, Len + 1);

stripped_len([], Len) ->
  Len.

バイナリにも同じ方法を適用できます。

于 2010-07-21T13:58:47.463 に答える