2010-07-21 1 views
6

Я пытаюсь получить среднюю длину fasta sequences с помощью Erlang. Файл FASTA выглядит следующим образом«Средняя длина последовательностей в файле fasta»: можете ли вы улучшить этот код Erlang?

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

Я попытался answser этот вопрос с помощью следующей 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, вы можете улучшить этот код?

+4

См. Также оригинальную «задачу»: http://biostar.stackexchange.com/questions/1759 – Pierre

+1

Вау, отличная коллекция нетривиальных образцов кода из широкого спектра языков. Благодаря! – sarnold

ответ

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. Compile так же, как 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 

Просто для полноты байт-код с ПКМ:

$ 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 

Для сравнения sarnoldversion дает мне неправильный ответ и занимает больше же HW:

$ 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 

EDIT: Я смотрел по характеристикам uniprot_sprot.fasta, и я немного удивлен. Это 3824397 строк и 232 МБ. Это означает, что версия -smp disabled может обрабатывать 1,18 миллиона текстовых строк в секунду (71 Мбайт/с в линейном ориентированном IO).

+0

очень интересно, спасибо. – Pierre

+0

Отлично! Спасибо за пример; можете ли вы указать, почему наши версии получают разные ответы? Благодаря! – sarnold

+0

@sarnold: У меня нет времени, чтобы посмотреть на вашу версию, где проблема. Я думаю, что это завершение «\ n», которое может быть не удалено 'string: strip/1', но я не уверен. Я проверил этот код 'perl -nle '/ ^> /? $ C++: ($ b + = length ((/ (\ S *) /) [0]))} {print $ b/$ c'' будет что у моей версии нет такой же ошибки, как и все остальные на http://biostar.stackexchange.com/questions/1759, но все кажется нормально, и 352.6697028442464 должен быть правильным ответом. –

3

Я тоже изучаю Erlang, спасибо за интересный вопрос.

Я понимаю работу с строками Эрланг, поскольку списки символов могут быть очень медленными; если вы можете использовать work with binaries, вместо этого вы должны увидеть прирост производительности. Я не знаю, как использовать строки произвольной длины с двоичными файлами, но если вы можете разобраться в них, это должно помочь.

Кроме того, если вы не против работать с файлом напрямую, а не с standard_io, возможно, вы могли бы ускорить работу, используя file:open(..., [raw, read_ahead]). raw означает, что файл должен находиться в файловой системе локального узла, а read_ahead указывает, что Erlang должен выполнить файл IO с буфером. (Подумайте о том, как использовать устройства C stdio с буферизацией и без нее.)

Я бы ожидал, что read_ahead сделает все возможное, но все, что касается Эрланг, включает в себя фразу «контрольная точка перед угаданием».

EDIT

Использование file:open("uniprot_sprot.fasta", [read, read_ahead]) получает 1m31s на полном uniprot_sprot.fasta данных. (Средняя 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(). 
+0

Спасибо Арнольд, я сейчас тестирую ваше решение. erl (version = R13B01) вызвал ошибку: «{« init завершение в do_boot », {undef, [{file, read_line, [{file_descriptor, prim_file, {# Port <0.286>, 7}}]}, {golf, scanLines, 3}, {golf, test, 0}, {init, start_it, 1}, {init, start_em, 1}]}} ". Есть идеи ? – Pierre

+0

Хорошо, я сказал, что моя версия не поддерживает R13B01, я буду тестировать это завтра на другом компьютере. – Pierre

2

Похоже, ваши большие проблемы с производительностью были решены путем открытия файла в нестандартном режиме, но вот еще некоторые мысли если вам нужно еще больше оптимизировать этот код.

Узнать и использовать fprof.

Вы используете string:strip/1 прежде всего для того, чтобы удалить конечную новую строку. Поскольку значения erlang неизменяемы, вы должны сделать полную копию списка (со всем соответствующим распределением памяти), чтобы удалить последний символ. Если вы знаете, что файл хорошо сформирован, просто вычтите его из своего счета, иначе я бы попытался написать функцию длины, подсчитывая количество соответствующих символов и игнорируя нерелевантные.

Я опасаюсь совета, который говорит, что двоичные файлы лучше, чем списки, но учитывая, как мало обрабатывается, вы, вероятно, здесь. Первые шаги - открыть файл в двоичном режиме и с помощью erlang:size/1 найти длину.

Это не повлияет на производительность (значительно), но умножение на 1,0 в Total/(1.0*Sequences) необходимо только на языках со сломанным делением. Отдел Эрланга работает правильно.

1

Вызов string:len(string:strip(L)) проходит по крайней мере в два раза (я не знаю строки: реализация полосы). Вместо того, чтобы вы могли написать простую функцию для подсчета длины линии ж/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. 

Того же метод может быть применен к бинарным файлам, а также.

0

Вы попробовали Elixir (elixir-lang.org), который запускается поверх Erlang и имеет синтаксис, похожий на Ruby. Эликсир решает проблемы строковых следующим образом:

Elixir строка UTF8 двоичных файлы, со всей сырой скоростью и памятью сбережения, которые приносят. Elixir имеет строковый модуль с встроенной функциональностью Unicode и является отличным примером написания кода, который пишет код. String.Unicode читает различные дампы базы данных Unicode, такие как как UnicodeData.txt для динамического создания функций Unicode для строкового модуля , построенного прямо из этих данных!(http://devintorr.es/blog/2013/01/22/the-excitement-of-elixir/)

Просто интересно, будет ли Эликсир быстрее?

+0

Вы не должны угадывать, а измерять. –

Смежные вопросы