“fasta文件中序列的平均长度”:你能改进这个Erlang代码吗?

时间:2010-07-21 06:43:24

标签: string erlang sequence bioinformatics mean

我正在尝试使用 Erlang 获得fasta sequences的平均长度。一个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)。为什么?我是Erlang的新手,请你改进这段代码吗?

5 个答案:

答案 0 :(得分: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

为了进行比较sarnold version给出了错误的答案并在同一个硬件上花费更多:

$ 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版本每秒可处理118万个文本行(面向行的IO为71MB / s)。

答案 1 :(得分:3)

我也在学习Erlang,感谢有趣的问题。

我理解使用Erlang字符串,因为字符列表可能非常慢;如果你可以work with binaries而不是你应该看到一些性能提升。我不知道你如何使用任意长度的字符串与二进制文件,但如果你可以解决它,它应该有所帮助。

此外,如果您不介意直接使用文件而不是standard_io,也许您可​​以使用file:open(..., [raw, read_ahead])加快速度。 raw表示文件必须位于本地节点的文件系统上,read_ahead指定Erlang应使用缓冲区执行文件IO。 (考虑使用C的stdio设施,无论是否有缓冲。)

我希望read_ahead能够发挥最大的作用,但是Erlang的所有内容都包含“猜测前的基准”这个短语。

修改

在完整的uniprot_sprot.fasta数据集上使用file:open("uniprot_sprot.fasta", [read, read_ahead])获取1m31s。 (平均359.04679841439776。)

使用file:open(.., [read, read_ahead])file:read_line(S),我得到0m34s

使用file:open(.., [read, read_ahead, raw])file:read_line(S),我得到0m9s。是的,九秒钟。

这是我现在的立场;如果你能弄清楚如何使用二进制文件而不是列表,它可能会看到更多改进:

-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().

答案 2 :(得分:2)

看起来你的大型性能问题已经通过在原始模式下打开文件来解决了,但是如果你需要进一步优化该代码,还有一些想法。

学习并使用fprof。

您主要使用string:strip/1删除尾随换行符。由于erlang值是不可变的,因此您必须创建列表的完整副本(包含所有相关的内存分配)才能删除最后一个字符。如果您知道文件格式正确,只需从计数中减去一个,否则我会尝试编写一个长度函数来计算相关字符的数量并忽略不相关的字符。

我对那些说二进制文件比列表更好的建议更加谨慎,但考虑到你处理它的可能性很小。第一步是以二进制模式打开文件,并使用erlang:size/1查找长度。

它不会影响性能(显着),但Total/(1.0*Sequences)中乘以1.0仅在断开的语言中是必需的。 Erlang部门工作正常。

答案 3 :(得分:1)

调用string:len(string:strip(L))遍历列表至少两次(我不知道字符串:strip实现)。相反,你可以编写一个简单的函数来计算行数w / 0的行长度:

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.

同样的方法也可以应用于二进制文件。

答案 4 :(得分:0)

您是否尝试过在Erlang之上运行的Elixir(elixir-lang.org)并且语法类似于Ruby。 Elixir通过以下方式解决字符串问题:

  

Elixir字符串是UTF8二进制文件,具有所有原始速度和内存   带来的节省。 Elixir有一个带Unicode的String模块   内置的功能,是编写代码的一个很好的例子   写代码。 String.Unicode读取各种Unicode数据库转储   作为UnicodeData.txt动态生成Unicode函数   字符串模块直接从该数据构建! (http://devintorr.es/blog/2013/01/22/the-excitement-of-elixir/

想知道Elixir会更快吗?