utilizzando parallel per elaborare file di input univoci in file di output univoci


18

Ho un problema di scripting della shell in cui mi viene fornita una directory piena di file di input (ogni file contenente molte righe di input) e devo elaborarli singolarmente, reindirizzando ciascuno dei loro output su un file univoco (ovvero file_1.input necessario per essere catturato in file_1.output e così via).

Prima del parallelismo , vorrei solo scorrere ogni file nella directory ed eseguire il mio comando, mentre eseguivo una sorta di tecnica timer / conteggio per non sovraccaricare i processori (supponendo che ogni processo avesse un runtime costante). Tuttavia, so che non sarà sempre il caso, quindi usare una soluzione simile a "parallela" sembra il modo migliore per ottenere il multi-threading di script di shell senza scrivere codice personalizzato.

Mentre ho pensato ad alcuni modi per montare in parallelo per elaborare ciascuno di questi file (e permettendomi di gestire i miei core in modo efficiente), sembrano tutti confusi. Ho quello che penso sia un caso d'uso piuttosto semplice, quindi preferirei tenerlo il più pulito possibile (e nulla negli esempi paralleli sembra saltar fuori come il mio problema.

Qualsiasi aiuto sarebbe apprezzato!

esempio di directory di input:

> ls -l input_files/
total 13355
location1.txt
location2.txt
location3.txt
location4.txt
location5.txt

script:

> cat proces_script.sh
#!/bin/sh

customScript -c 33 -I -file [inputFile] -a -v 55 > [outputFile]

Aggiornamento : dopo aver letto la risposta di Ole di seguito, sono stato in grado di mettere insieme i pezzi mancanti per la mia implementazione parallela. Mentre la sua risposta è ottima, ecco la mia ricerca aggiuntiva e le note che ho preso:

Invece di eseguire l'intero processo, ho pensato di iniziare con un comando proof of concept per provare la sua soluzione nel mio ambiente. Vedi le mie due diverse implementazioni (e note):

find /home/me/input_files -type f -name *.txt | parallel cat /home/me/input_files/{} '>' /home/me/output_files/{.}.out

Usa find (non ls, che può causare problemi) per trovare tutti i file applicabili nella mia directory dei file di input, quindi reindirizza i loro contenuti in una directory e file separati. Il mio problema dall'alto era la lettura e il reindirizzamento (la sceneggiatura reale era semplice), quindi sostituire la sceneggiatura con cat era una bella prova del concetto.

parallel cat '>' /home/me/output_files/{.}.out :::  /home/me/input_files/*

Questa seconda soluzione utilizza il paradigma della variabile di input di parallel per leggere i file, tuttavia per un principiante, questo era molto più confuso. Per me, usare find a e pipe ha soddisfatto perfettamente le mie esigenze.

Risposte:


27

GNU Parallel è progettato per questo tipo di attività:

parallel customScript -c 33 -I -file {} -a -v 55 '>' {.}.output ::: *.input

o:

ls | parallel customScript -c 33 -I -file {} -a -v 55 '>' {.}.output

Verrà eseguito un lavoro per core della CPU.

Puoi installare GNU Parallel semplicemente:

wget https://git.savannah.gnu.org/cgit/parallel.git/plain/src/parallel
chmod 755 parallel
cp parallel sem

Guarda i video introduttivi di GNU Parallel per saperne di più: https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1


Ottima risposta (e punti importanti per la lettura della mia richiesta di utilizzo in parallelo).
J Jones,

5

Il modo standard per farlo è impostare una coda e generare un numero qualsiasi di lavoratori che sanno come estrarre qualcosa dalla coda ed elaborarlo. È possibile utilizzare un fifo (detto anche pipe) per la comunicazione tra questi processi.

Di seguito è riportato un esempio ingenuo per dimostrare il concetto.

Un semplice script di coda:

#!/bin/sh
mkfifo /tmp/location-queue
for i in inputfiles/*; do
  echo $i > /tmp/location-queue
done
rm /tmp/location-queue

E un lavoratore:

#!/bin/sh
while read file < /tmp/location-queue; do
  process_file "$file"
done

process_file potrebbe essere definito da qualche parte nel tuo lavoratore e può fare tutto ciò di cui hai bisogno.

Una volta che hai quei due pezzi, puoi avere un semplice monitor che avvia il processo di coda e qualsiasi numero di processi di lavoro.

Monitorare lo script:

#!/bin/sh
queue.sh &
num_workers="$1"
i=0
while [ $i < $num_workers ]; do
  worker.sh &
  echo $! >> /tmp/worker.pids
  i=$((i+1))
done
monitor_workers

Ecco qua. Se lo fai effettivamente, è meglio impostare il fifo sul monitor e passare il percorso sia alla coda che ai lavoratori, in modo che non siano accoppiati e non siano bloccati in una posizione specifica per il fifo. L'ho impostato in questo modo nella risposta in modo specifico, quindi è chiaro che cosa stai usando mentre lo leggi.


In che modo il monitor è abbastanza intelligente da mettere in pausa la generazione di nuovi lavoratori fino al termine del prossimo (ovvero, dove mai decrementano $)? ---- Rispondendo alla mia modifica, i lavoratori non vanno mai via, elaborano solo i file fino a quando tutta l'elaborazione non è stata esaurita (quindi anche il ciclo while all'interno dei 'processori').
J Jones,

Che cos'è la riga "monitor_workers" alla fine dello script monitor?
J Jones,

@JJones - monitor_workersè proprio come process_file- è una funzione che fa quello che vuoi. A proposito del monitor - avevi ragione; dovrebbe salvare i pid dei suoi lavoratori (in modo che possa inviare un segnale di uccisione) e il contatore deve essere incrementato quando avvia un lavoratore. Ho modificato la risposta per includerla.
Shawn J. Goff,

Apprezzo molto il tuo lavoro, ma penso che dovresti usare GNU parallel. Penso che sia una tua idea, pienamente implementata.
motobói,

5

Un altro esempio:

ls *.txt | parallel 'sort {} > {.}.sorted.txt'

Ho trovato gli altri esempi inutilmente complessi, quando nella maggior parte dei casi quanto sopra è quello che potresti aver cercato.


4

Viene creato uno strumento comunemente disponibile che può eseguire la parallelizzazione. GNU make e pochi altri hanno -jun'opzione per eseguire build parallele.

.SUFFIXES: .input .output
.input.output:
        process_one_file <$< >$@.tmp
        mv -f $@.tmp $@

Esegui in makequesto modo (suppongo che i nomi dei tuoi file non contengano caratteri speciali, makenon va bene con quelli):

make -j 4 $(for x in *.input; do echo ${x%.*}.output; done)

imho questa è la soluzione più intelligente :)
h4unt3r

3

Questo per eseguire lo stesso comando su un ampio set di file nella directory corrente:

#!/bin/sh
trap 'worker=`expr $worker - 1`' USR1  # free up a worker
worker=0  # current worker
num_workers=10  # maximum number of workers
for file in *.txt; do
    if [ $worker -lt $num_workers ]; then
        {   customScript -c 33 -I -file $file -a -v 55 > `basename $file .txt`.outtxt 
            kill -USR1 $$ 2>/dev/null  # signal parent that we're free
        } &
        echo $worker/$num_worker $! $file  # feedback to caller
        worker=`expr $worker + 1`
    else
        wait # for a worker to finish
    fi
done

Questo esegue il customScriptsu ogni txtfile, mettendo l'output in outtxtfile. Cambia di cui hai bisogno. La chiave per farlo funzionare è l'elaborazione del segnale, usando SIGUSR1 in modo che il processo figlio possa far sapere al processo genitore che è stato fatto. L'uso di SIGCHLD non funzionerà poiché la maggior parte delle istruzioni nello script genererà segnali SIGCHLD allo script della shell. Ho provato a sostituire questo comando con sleep 1il programma che utilizzava 0,28 secondi di CPU utente e 0,14 secondi di CPU di sistema; questo era solo su circa 400 file.


In che modo 'wait' è abbastanza intelligente da prendere lo stesso file che è attualmente iterato e reinserire l'istruzione "if" del fratello?
J Jones,

Non è waitabbastanza "intelligente"; ma tornerà dopo aver ricevuto il SIGUSR1segnale. Il figlio / lavoratore invia SIGUSR1a al genitore, che viene catturato ( trap), e diminuisce $worker( trapclausola) e ritorna in modo anomalo da wait, consentendo l' if [ $worker -lt $num_workers ]esecuzione della clausola.
Arcege,

0

O semplicemente usare xargs -P, non è necessario installare software aggiuntivo:

find . -type f -print0 | xargs -0 -I'XXX' -P4 -n1 custom_script -input "XXX" -output "XXX.out"

Un po 'di spiegazione per le opzioni:

  • -I'XXX' imposta la stringa che verrà sostituita nel modello di comando con il nome del file
  • -P4 eseguirà 4 processi in parallelo
  • -n1 inserirà un solo file per esecuzione anche se vengono trovati due XXX
  • -print0e -0lavorare insieme, permettendoti di avere caratteri speciali (come spazi bianchi) nei nomi dei file
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.