цикл while работает параллельно с вводом из разделенного файла - PullRequest
0 голосов
/ 20 декабря 2018

Я застрял на этом.Так что в моем коде есть такой цикл while-read, который занимает так много времени, и я хотел бы запустить его во многих процессорах.Но я хотел бы разделить входной файл и запустить 14 циклов (потому что у меня 14 потоков), по одному для каждого разделенного файла, параллельно.Дело в том, что я не знаю, как сказать циклу while, какой файл получить и с чем работать.

Например, в обычном цикле while-read я бы написал:

while read line
do
   <some code>
done < input file or variable...

Но в этом случае я хотел бы разбить указанный выше входной файл на 14 файлов и запустить 14 циклов while параллельно, по одному для каждого разделенного файла.Я пытался:

split -n 14 input_file
find . -name "xa*" | \
        parallel -j 14 | \
        while read line
        do
        <lot of stuff>
        done

также пытался

split -n 14 input_file
function loop {
            while read line
            do
                <lot of stuff>
            done
}
export -f loop
parallel -j 14 ::: loop 

Но я также не мог сказать, какой файл будет входом для цикла, так что параллель поймет, "возьмите каждый из этих xa *файлы и поместить в отдельные циклы параллельно "

Пример входного файла (список строк)

AEYS01000010.10484.12283
CVJT01000011.50.2173
KF625180.1.1799
KT949922.1.1791
LOBZ01000025.54942.57580

РЕДАКТИРОВАТЬ

Thisэто код.В результате получается таблица (741100 строк) с некоторой статистикой, касающейся уже сделанных выравниваний последовательностей ДНК.Цикл принимает файл input_file (без ломаных линий, изменяется от 500 до ~ 45000 строк, 800Kb) с последовательностями последовательности ДНК, читает его построчно и ищет каждую соответствующую полную таксономию для этих последовательностей в банке данных (~ 45000 строк),Затем он делает несколько сумм / делений.Выходные данные являются .tsv и выглядят так (пример для последовательности "KF625180.1.1799"):

Rate of taxonomies for this sequence in %:        KF625180.1.1799 D_6__Bacillus_atrophaeus
Taxonomy %aligned number_ocurrences_in_the_alignment     num_ocurrences_in_databank    %alingment/databank
D_6__Bacillus_atrophaeus   50%     1       20      5%
D_6__Bacillus_amyloliquefaciens    50%     1       154     0.649351%



$ head input file  
AEYS01000010.10484.12283
CVJT01000011.50.217
KF625180.1.1799
KT949922.1.1791
LOBZ01000025.54942.57580

Два дополнительных файла также используются внутри цикла.Они не являются входом для петли.1) файл с именем alnout_file, который служит только для определения количества попаданий (или выравниваний) данной последовательности в банк данных.Это было также ранее сделано вне этой петли.Это может варьироваться в количестве строк от сотен до тысяч.Здесь важны только столбцы 1 и 2.Column1 - это имя последовательности, а col2 - это имя всех последовательностей, с которыми оно совпало в databnk.Это выглядит так:

$ head alnout_file
KF625180.1.1799 KF625180.1.1799 100.0   431     0       0       1       431     1       431     -1      0
KF625180.1.1799 KP143082.1.1457 99.3    431     1       2       1       431     1       429     -1      0
KP143082.1.1457 KF625180.1.1799 99.3    431     1       2       1       429     1       431     -1      0    

2) файл .tsv банка данных, содержащий ~ 45000 таксономий, соответствующих последовательностям ДНК.Каждая таксономия находится в одной строке:

$ head taxonomy.file.tsv
KP143082.1.1457 D_0__Bacteria;D_1__Firmicutes;D_2__Bacilli;D_3__Bacillales;D_4__Bacillaceae;D_5__Bacillus;D_6__Bacillus_amyloliquefaciens
KF625180.1.1799 D_0__Bacteria;D_1__Firmicutes;D_2__Bacilli;D_3__Bacillales;D_4__Bacillaceae;D_5__Bacillus;D_6__Bacillus_atrophaeus

Итак, задана последовательность KF625180.1.1799.Ранее я выровнял его по банку данных, содержащему ~ 45000 других последовательностей ДНК, и получил вывод, который имеет все присоединения к последовательностям, которые он соответствовал.Цикл состоит в том, что он находит таксономии для всех этих последовательностей и вычисляет «статистику», о которой я упоминал ранее.Код делает это для всех доступных мне последовательностей ДНК-последовательностей.

TAXONOMY=path/taxonomy.file.tsv
while read line
do
#find hits
        hits=$(grep $line alnout_file | cut -f 2)
        completename=$(grep $line $TAXONOMY | sed 's/D_0.*D_4/D_4/g')
        printf "\nRate of taxonomies for this sequence in %%:\t$completename\n"
        printf "Taxonomy\t%aligned\tnumber_ocurrences_in_the_alignment\tnum_ocurrences_in_databank\t%alingment/databank\n"

        #find hits and calculate the frequence (%) of the taxonomy in the alignment output
        # ex.: Bacillus_subtilis 33
        freqHits=$(grep "${hits[@]}" $TAXONOMY | \
                cut -f 2 | \
                awk '{a[$0]++} END {for (i in a) {print i, "\t", a[i]/NR*100, "\t", a[i]}}' | \
                sed -e 's/D_0.*D_5/D_5/g' -e 's#\s\t\s#\t#g' | \
                sort -k2 -hr)

        # print frequence of each taxonomy in the databank

        freqBank=$(while read line; do grep -c "$line" $TAXONOMY; done < <(echo "$freqHits" | cut -f 1))
        #print cols with taxonomy and calculations
        paste <(printf %s "$freqHits") <(printf %s "$freqBank") | awk '{print $1,"\t",$2"%","\t",$3,"\t",$4,"\t",$3/$4*100"%"}'

done < input_file

Это много greps и парсинга, так что требуется около 12 часов работы в одном процессоре для выполнения всех 45000 последовательностей ДНК.вступления.Я хотел бы разделить input_file и сделать это на всех процессорах, которые у меня есть (14), потому что это будет время, потраченное на это.Спасибо всем за то, что вы так терпеливы со мной =)

Ответы [ 6 ]

0 голосов
/ 23 декабря 2018

Я думаю, что использование набора команд grep и awk является неправильным подходом - вам было бы намного лучше, если бы вы использовали Perl или awk.Поскольку вы не предоставили примеров файлов, я сгенерировал некоторые из них, используя этот код:

#!/bin/bash

for a in {A..Z} {0..9} ; do
   for b in {A..Z} {0..9} ; do
      for c in {A..Z} {0..9} ; do
         echo "${a}${b}${c}"
      done
   done
done > a

# Now make file "b" which has the same stuff but shuffled into a different order
gshuf < a > b

Обратите внимание, что в алфавите 26 букв, поэтому, если я добавлю цифры 0,9 к буквам алфавитаЯ получаю 36 буквенно-цифровых цифр, и если я вложу 3 цикла, я получу 36^3 или 46 656 строк, что примерно соответствует размеру вашего файла.Файл a теперь выглядит следующим образом:

AAA
AAB
AAC
AAD
AAE
AAF

Файл b выглядит следующим образом:

UKM
L50
AOC
79U
K6S
6PO
12I
XEV
WJN

Теперь я хочу перебрать a и найти соответствующую строку в b.Во-первых, я использую ваш подход:

time while read thing ; do grep $thing b > /dev/null ; done < a

Это занимает 9 минут 35 секунд .

Если я сейчас выхожу из grep в первом матче, в среднем яЯ найду его посередине, что означает, что время сократится вдвое, поскольку я не буду продолжать без необходимости читать b после того, как найду то, что хочу.

time while read thing ; do grep -m1 $thing b > /dev/null ; done < a

Это сокращает время до 4 минуты 30 секунд .

Если я сейчас использую awk, чтобы прочитать содержимое b в ассоциативный массив (он же хэш), а затем прочитать элементы a и найти ихв b вот так:

time awk 'FNR==NR{a[$1]=$1; next} {print a[$1]}' b a > /dev/null

Теперь он запускается за 0,07 секунды .Надеюсь, вы поняли, к чему я клоню.Я ожидаю, что Perl сделает это в то же время, а также предоставит более выразительные возможности для математики в середине цикла.

0 голосов
/ 23 декабря 2018

Вы ищете --pipe.В этом случае вы даже можете использовать оптимизированный --pipepart (версия> 20160621):

export TAXONOMY=path/taxonomy.file.tsv
doit() {
while read line
do
#find hits
        hits=$(grep $line alnout_file | cut -f 2)
        completename=$(grep $line $TAXONOMY | sed 's/D_0.*D_4/D_4/g')
        printf "\nRate of taxonomies for this sequence in %%:\t$completename\n"
        printf "Taxonomy\t%aligned\tnumber_ocurrences_in_the_alignment\tnum_ocurrences_in_databank\t%alingment/databank\n"

        #find hits and calculate the frequence (%) of the taxonomy in the alignment output
        # ex.: Bacillus_subtilis 33
        freqHits=$(grep "${hits[@]}" $TAXONOMY | \
                cut -f 2 | \
                awk '{a[$0]++} END {for (i in a) {print i, "\t", a[i]/NR*100, "\t", a[i]}}' | \
                sed -e 's/D_0.*D_5/D_5/g' -e 's#\s\t\s#\t#g' | \
                sort -k2 -hr)

        # print frequence of each taxonomy in the databank

        freqBank=$(while read line; do grep -c "$line" $TAXONOMY; done < <(echo "$freqHits" | cut -f 1))
        #print cols with taxonomy and calculations
        paste <(printf %s "$freqHits") <(printf %s "$freqBank") | awk '{print $1,"\t",$2"%","\t",$3,"\t",$4,"\t",$3/$4*100"%"}'

done
}
export -f doit
parallel -a input_file --pipepart doit

Это позволит разделить файл input_file на 10 * блоков ncpu (где ncpu - количество потоков ЦП), передавая каждый блокна doit, запустите задания ncpu параллельно.

Тем не менее, я думаю, что ваша настоящая проблема заключается в создании слишком большого количества программ: если вы переписываете doit в Perl или Python, я ожидаю, что вы увидите значительное ускорение.

0 голосов
/ 21 декабря 2018

Это отвечает на ваш вопрос, а именно, как параллельно обрабатывать 14 файлов, которые вы получаете при запуске split.Тем не менее, я не думаю, что это лучший способ сделать то, что вы пытаетесь сделать, - но для этого нам потребуются некоторые ответы от вас.

Итак, давайте создадим файл с миллионами строк иразделить его на 14 частей:

seq 1000000 > 1M
split -n 14 1M part-

Это дает мне 14 файлов с именами от part-aa до part-an.Теперь ваш вопрос заключается в том, как обрабатывать эти 14 частей параллельно - (сначала прочитайте последнюю строку):

#!/bin/bash

# This function will be called for each of the 14 files
DoOne(){
   # Pick up parameters
   job=$1
   file=$2
   # Count lines in specified file
   lines=$(wc -l < "$file")
   echo "Job No: $job, file: $file, lines: $lines"
}

# Make the function above known to processes spawned by GNU Parallel
export -f DoOne

# Run 14 parallel instances of "DoOne" passing job number and filename to each
parallel -k -j 14 DoOne {#} {} ::: part-??

Пример вывода

Job No: 1, file: part-aa, lines:    83861
Job No: 2, file: part-ab, lines:    72600
Job No: 3, file: part-ac, lines:    70295
Job No: 4, file: part-ad, lines:    70295
Job No: 5, file: part-ae, lines:    70294
Job No: 6, file: part-af, lines:    70295
Job No: 7, file: part-ag, lines:    70295
Job No: 8, file: part-ah, lines:    70294
Job No: 9, file: part-ai, lines:    70295
Job No: 10, file: part-aj, lines:    70295
Job No: 11, file: part-ak, lines:    70295
Job No: 12, file: part-al, lines:    70294
Job No: 13, file: part-am, lines:    70295
Job No: 14, file: part-an, lines:    70297

Вы бы пропустили-k аргумент GNU Parallel обычно - я только добавил его, так что вывод идет по порядку.

0 голосов
/ 20 декабря 2018

В качестве альтернативы я собрал быстрый тест.

#! /bin/env bash
mkfifo PIPELINE             # create a single queue
cat "$1" > PIPELINE &       # supply it with records
{ declare -i cnt=0 max=14
  while (( ++cnt <= max ))  # spawn loop creates worker jobs
  do printf -v fn "%02d" $cnt
     while read -r line     # each work loop reads common stdin...
     do echo "$fn:[$line]"
        sleep 1
     done >$fn.log 2>&1 &   # these run in background in parallel
  done                      # this one exits
} < PIPELINE                # *all* read from the same queue
wait
cat [0-9][0-9].log

Не нужно split, но нужно mkfifo.

Очевидно, измените код внутривнутренний цикл.

0 голосов
/ 20 декабря 2018

Это может сделать работу для Вас, я не знаком с параллелью вместо использования собственных процессов порождения bash &:

function loop () {
  while IFS= read -r -d $'\n'
  do
    # YOUR BIG STUFF
  done < "${1}"
}

arr_files=(./xa*)
for i in "${arr_files[@]}"
do loop "${i}" &
done
wait
0 голосов
/ 20 декабря 2018

Надеюсь, этот небольшой скрипт поможет вам:

function process {
    while read line; do
        echo "$line"
    done < $1
}

function loop {
    file=$1
    chunks=$2
    dir=`mktemp -d`
    cd $dir
    split -n l/$chunks $file
    for i in *; do
        process "$i" &
    done
    rm -rf $dir
}

loop /tmp/foo 14

Он запускает цикл обработки указанного файла с указанным количеством кусков (без разделения строк) параллельно (используя & для установкикаждый вызов в фоновом режиме).Я надеюсь, это поможет вам.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...