Esecuzione di comandi inoltrati in parallelo


16

Considera il seguente scenario. Ho due programmi A e B. Le uscite del programma A per le linee di stringhe stdout mentre le linee di processo del programma B da stdin. Il modo di usare questi due programmi è ovviamente:

foo @ bar: ~ $ A | B

Ora ho notato che questo mangia solo un nucleo; quindi mi chiedo:

I programmi A e B condividono le stesse risorse computazionali? In tal caso, esiste un modo per eseguire A e B contemporaneamente?

Un'altra cosa che ho notato è che A funziona molto più velocemente di B, quindi mi chiedo se potrebbe in qualche modo eseguire più programmi B e consentire loro di elaborare le linee che A genera in parallelo.

Cioè, A produrrebbe le sue righe e ci sarebbero N istanze di programmi B che leggessero queste righe (chiunque le leggesse per prime) le elabora e le emette su stdout.

Quindi la mia ultima domanda è:

Esiste un modo per convogliare l'output ad A tra diversi processi B senza doversi occupare delle condizioni di gara e di altre incoerenze che potrebbero potenzialmente insorgere?


1
Mentre A | B | Cè parallelo come in processi separati, a causa della natura dei tubi (B deve attendere l'uscita di A, C deve attendere l'uscita di B) in alcuni casi può essere comunque lineare. Dipende interamente dal tipo di output che producono. Non ci sono molti casi in cui l'esecuzione di più Bpotrebbe essere di grande aiuto, è del tutto possibile che l'esempio di wc parallelo sia più lento del normale wcpoiché la divisione può richiedere più risorse rispetto al conteggio delle linee normalmente. Usare con cura.
frostschutz,

Risposte:


14

Un problema split --filterè che l'output può essere confuso, in modo da ottenere mezza riga dal processo 1 seguita da mezza riga dal processo 2.

GNU Parallel garantisce che non ci sarà confusione.

Quindi supponiamo che tu voglia fare:

 A | B | C

Ma quella B è terribilmente lenta, e quindi vuoi parallelizzarla. Quindi puoi fare:

A | parallel --pipe B | C

GNU Parallel per impostazione predefinita si divide su \ n e una dimensione del blocco di 1 MB. Questo può essere regolato con --recend e --block.

Puoi trovare ulteriori informazioni su GNU Parallel su: http://www.gnu.org/s/parallel/

Puoi installare GNU Parallel in soli 10 secondi con:

wget -O - pi.dk/3 | sh 

Guarda il video introduttivo su http://www.youtube.com/playlist?list=PL284C9FF2488BC6D1


1
Mentre non sono assolutamente d'accordo sul metodo di installazione :-), +1 perché la tua soluzione risolve la maggior parte dei problemi con la mia.
LSerni,

Questo è davvero carino. Hai anche qualche suggerimento per i parametri da usare? So che il programma A produrrà più di 1 TB di dati circa 5 GB al minuto. Il programma B elabora i dati 5 volte più lentamente di A e li ho a disposizione 5 core per questo compito.
Jernej,

GNU Parallel attualmente può gestire al massimo circa 100 MB / s, quindi toccherai quel limite. L'ottimale --block-sizedipenderà dalla quantità di RAM e dalla velocità con cui puoi avviarne una nuova B. Nella tua situazione vorrei usare --block 100Me vedere come ha funzionato.
Ole Tange,

@lserni Puoi trovare un metodo di installazione migliore, che funziona sulla maggior parte delle macchine UNIX e richiede un simile lavoro da parte dell'utente?
Ole Tange,

4
Scusa, non mi sono chiarito. Il metodo di installazione - lo script passato a sh- è fantastico. Il problema sta nel passare a sh: download ed esecuzione di codice eseguibile da un sito . Intendiamoci, forse sono solo troppo paranoico, dal momento che si potrebbe obiettare che un RPM o DEB su misura è sostanzialmente la stessa cosa, e persino pubblicare il codice su una pagina per essere copiato e incollato comporterebbe persone che lo fanno alla cieca Comunque.
LSerni,

13

Quando scrivi A | B, entrambi i processi già eseguiti in parallelo. Se li vedi utilizzando solo un core, probabilmente è a causa delle impostazioni di affinità della CPU (forse c'è qualche strumento per generare un processo con affinità diversa) o perché un processo non è sufficiente per contenere un intero core e il sistema " preferisce "non diffondere il calcolo.

Per eseguire diverse B con una A, è necessario uno strumento come splitcon l' --filteropzione:

A | split [OPTIONS] --filter="B"

Questo, tuttavia, rischia di incasinare l'ordine delle linee nell'output, perché i lavori B non verranno eseguiti tutti alla stessa velocità. Se questo è un problema, potrebbe essere necessario reindirizzare l'output B in un file intermedio e cucirli insieme alla fine usando cat. Questo, a sua volta, può richiedere un notevole spazio su disco.

Esistono altre opzioni (ad esempio, è possibile limitare ciascuna istanza di B a un output con buffer a riga singola, attendere fino al termine di un intero "giro" di B, eseguire l'equivalente di una riduzione per splitla mappa e catl'output temporaneo insieme), con diversi livelli di efficienza. L'opzione 'round' appena descritta, ad esempio, attenderà il completamento dell'istanza più lenta di B , quindi dipenderà fortemente dal buffering disponibile per B; [m]bufferpotrebbe aiutare, oppure no, a seconda delle operazioni.

Esempi

Genera i primi 1000 numeri e conta le linee in parallelo:

seq 1 1000 | split -n r/10 -u --filter="wc -l"
100
100
100
100
100
100
100
100
100
100

Se dovessimo "contrassegnare" le linee, vedremmo che ogni prima riga viene inviata al processo n. 1, ogni quinta riga al processo n. 5 e così via. Inoltre, nel tempo necessario splitper generare il secondo processo, il primo è già un buon modo per la sua quota:

seq 1 1000 | split -n r/10 -u --filter="sed -e 's/^/$RANDOM - /g'" | head -n 10
19190 - 1
19190 - 11
19190 - 21
19190 - 31
19190 - 41
19190 - 51
19190 - 61
19190 - 71
19190 - 81

Quando si esegue su una macchina a 2 core seq, spliti wcprocessi condividono i core; ma guardando più da vicino, il sistema lascia i primi due processi su CPU0 e divide CPU1 tra i processi di lavoro:

%Cpu0  : 47.2 us, 13.7 sy,  0.0 ni, 38.1 id,  1.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu1  : 15.8 us, 82.9 sy,  0.0 ni,  1.0 id,  0.0 wa,  0.3 hi,  0.0 si,  0.0 st
  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM     TIME+ COMMAND
 5314 lserni    20   0  4516  568  476 R 23.9  0.0   0:03.30 seq
 5315 lserni    20   0  4580  720  608 R 52.5  0.0   0:07.32 split
 5317 lserni    20   0  4520  576  484 S 13.3  0.0   0:01.86 wc
 5318 lserni    20   0  4520  572  484 S 14.0  0.0   0:01.88 wc
 5319 lserni    20   0  4520  576  484 S 13.6  0.0   0:01.88 wc
 5320 lserni    20   0  4520  576  484 S 13.3  0.0   0:01.85 wc
 5321 lserni    20   0  4520  572  484 S 13.3  0.0   0:01.84 wc
 5322 lserni    20   0  4520  576  484 S 13.3  0.0   0:01.86 wc
 5323 lserni    20   0  4520  576  484 S 13.3  0.0   0:01.86 wc
 5324 lserni    20   0  4520  576  484 S 13.3  0.0   0:01.87 wc

Si noti in particolare che splitsta consumando una notevole quantità di CPU. Ciò diminuirà in proporzione alle esigenze di A; cioè, se A è un processo più pesante di seq, il relativo sovraccarico splitdiminuirà. Ma se A è un processo molto leggero e B è piuttosto veloce (quindi non hai bisogno di più di 2-3 B per tenerlo insieme ad A), allora la parallelizzazione con split(o i tubi in generale) potrebbe non valerne la pena.


Interessante che la divisione trovata su Ubuntu non abbia l'opzione --filter. Che tipo di sistema operativo utilizza per questo?
Jernej,

Linux OpenSuSE 12.3, con coreutils ( gnu.org/software/coreutils/manual/html_node/… ). Proverò a procurarmi un Ubuntu, potrebbero aver cambiato il nome per ospitare uno strumento con un nome simile.
LSerni,

Sei sicuro split --filterdell'opzione mancante? Sul mio Ubuntu 12.04-LTS ("wheezy / sid"), è lì, e i miei esempi funzionano. splitAvresti potuto installarne uno diverso da quello nei coreutils GNU?
LSerni,

Grazie per questo. Ho dovuto installare una versione più recente di Coreutils. A proposito, ho notato che se eseguo il programma A da solo, mangia un intero core (100%) se eseguo A | B poi mangiano insieme un intero nucleo, elaborano A mangiando il 15% e trasformano B mangiando l'85%. Capiti perché è così?
Jernej,

2
Ciò è probabilmente a causa del blocco . Se B è più pesante di A, A non può inviare il suo output e viene rallentato. Un'altra possibilità è che A ceda a B durante il suo funzionamento (ad es. Disco / rete). Su un sistema diverso, è possibile che B assorba il 100% della CPU1 e che A sia assegnato al 18% della CPU0. Probabilmente avrai bisogno di 85/15 ~ 5.67 = tra 5 e 6 istanze di B per ottenere una singola istanza A per saturare un singolo core. Tuttavia, l'I / O, se presente, potrebbe distorcere questi valori.
LSerni,
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.