Utilizzo delle risorse usando pipe e qui stringa


16

Possiamo ottenere lo stesso risultato usando i seguenti due in bash ,

echo 'foo' | cat

e

cat <<< 'foo'

La mia domanda è: quali sono le differenze tra questi due per quanto riguarda le risorse utilizzate e quale è meglio?

Il mio pensiero è che durante l'utilizzo di pipe utilizziamo un processo aggiuntivo echoe pipe mentre in questa stringa viene utilizzato solo un descrittore di file cat.

Risposte:


17

La pipe è un file aperto in un file system nel kernel e non è accessibile come normale file su disco. Viene automaticamente bufferizzato solo per una determinata dimensione e alla fine si bloccherà quando pieno. A differenza dei file provenienti da dispositivi a blocchi, le pipe si comportano in modo molto simile ai dispositivi a caratteri e quindi generalmente non supportanolseek() e i dati letti da essi non possono essere letti nuovamente come si farebbe con un file normale.

La stringa qui è un file normale creato in un file system montato. La shell crea il file e mantiene il suo descrittore rimuovendo immediatamente il suo unico collegamento al file system (e quindi eliminandolo) prima di scrivere / leggere un byte nel / dal file. Il kernel manterrà lo spazio richiesto per il file fino a quando tutti i processi non rilasceranno tutti i descrittori per esso. Se il bambino che legge da un tale descrittore ha la capacità di farlo, può essere riavvolto lseek()e letto di nuovo.

In entrambi i casi i token <<<e |rappresentano i descrittori di file e non necessariamente i file stessi. Puoi avere un'idea migliore di ciò che sta accadendo facendo cose come:

readlink /dev/fd/1 | cat

...o...

ls -l <<<'' /dev/fd/*

La differenza più significativa tra i due file è che qui-string / doc è praticamente un affare tutto in una volta - la shell scrive tutti i dati in esso prima di offrire il descrittore di lettura al figlio. D'altra parte, la shell apre la pipe sui descrittori appropriati e fa in modo che i figli si allontanino per gestire quelli per la pipe - e quindi è scritta / letta contemporaneamente ad entrambe le estremità.

Queste distinzioni, tuttavia, sono generalmente vere. Per quanto ne so (che non è poi così lontano) questo vale praticamente per ogni shell che gestisce la scorciatoia <<<qui-stringa per <<un reindirizzamento del documento qui con la sola eccezione di yash. yash, busybox, dash, E altriash varianti tendono a tornare qui-documenti con i tubi, però, e quindi in quei gusci c'è davvero poca differenza tra i due, dopo tutto.

Ok - due eccezioni. Ora che ci sto pensando, in ksh93realtà non fa affatto una pipa per |, ma piuttosto gestisce l'intera azienda con i socket - sebbene faccia un file tmp cancellato per <<<*la maggior parte degli altri. Inoltre, inserisce solo le sezioni separate di una pipeline in un ambiente subshell che è una sorta di eufemismo POSIX per almeno agire come una subshell , e quindi non fa nemmeno le forcelle.

Il fatto è che i risultati del benchmark di @ PSkocik (che è molto utile) qui possono variare notevolmente per molte ragioni e la maggior parte di questi dipende dall'implementazione. Per la configurazione del documento qui i fattori principali saranno il ${TMPDIR}tipo di file system di destinazione e la configurazione / disponibilità della cache corrente, e comunque la quantità di dati da scrivere. Per la pipa avrà le dimensioni del processo di shell stesso, poiché le copie vengono eseguite per le forcelle richieste. In questo modo bashè terribile nella configurazione della pipeline (per includere le sostituzioni di $(comandi )) , perché è grande e molto lento, ma con ksh93esso non fa praticamente alcuna differenza.

Ecco un altro piccolo frammento di shell per dimostrare come una shell si divide dai subshells per una pipeline:

pipe_who(){ echo "$$"; sh -c 'echo "$PPID"'; }
pipe_who
pipe_who | { pipe_who | cat /dev/fd/3 -; } 3<&0

32059  #bash's pid
32059  #sh's ppid
32059  #1st subshell's $$
32111  #1st subshell sh's ppid
32059  #2cd subshell's $$
32114  #2cd subshell sh's ppid

La differenza tra ciò che pipe_who()riporta una chiamata pipeline e il rapporto di un'esecuzione nella shell corrente è dovuta al comportamento specificato da una (subshell )di rivendicare il pid della shell padre $$quando viene espansa. Sebbene le bashsubshells siano sicuramente processi separati, lo $$speciale parametro shell non è una fonte affidabile di queste informazioni. Tuttavia, la shshell figlio della subshell non si rifiuta di riportare accuratamente la sua $PPID.


Molto utile. Il filesystem nel kernel, c'è un nome per questo? significa che esiste nello spazio del kernel?
utlamn

2
@utlamn - in realtà sì - semplicemente pipefs . È tutto in-kernel - ma (a parte cose come FUSE) così è tutto l' i / o dei file .
Mikeserv,

10

Non c'è sostituto per il benchmarking:

pskocik@ProBook:~ 
$ time (for((i=0;i<1000;i++)); do cat<<< foo >/dev/null; done  )

real    0m2.080s
user    0m0.738s
sys 0m1.439s
pskocik@ProBook:~ 
$ time (for((i=0;i<1000;i++)); do echo foo |cat >/dev/null; done  )

real    0m4.432s
user    0m2.095s
sys 0m3.927s
$ time (for((i=0;i<1000;i++)); do cat <(echo foo) >/dev/null; done  )
real    0m3.380s
user    0m1.121s
sys 0m3.423s

E per una maggiore quantità di dati:

TENMEG=$(ruby -e 'puts "A"*(10*1024*1024)')
pskocik@ProBook:~ 
$ time (for((i=0;i<100;i++)); do echo "$TENMEG" |cat >/dev/null; done  )

real    0m42.327s
user    0m38.591s
sys 0m4.226s
pskocik@ProBook:~ 
$ time (for((i=0;i<100;i++)); do cat<<< "$TENMEG" >/dev/null; done  )

real    1m26.946s
user    1m23.116s
sys 0m3.681s
pskocik@ProBook:~ 

$ time (for((i=0;i<100;i++)); do cat <(echo "$TENMEG") >/dev/null; done  )

real    0m43.910s
user    0m40.178s
sys 0m4.119s

Sembrerebbe che la versione di pipe abbia un costo di installazione maggiore ma alla fine sia più efficiente.


@mikeserv Era corretto. Ho aggiunto un benchmark con una maggiore quantità di dati.
PSkocik,

2
echo foo >/dev/shm/1;cat /dev/shm/1 >/dev/nullsembrava essere veloce in entrambi i casi ...
user23013

@ user23013 Ha senso. Non vedo perché echo "$longstring"o <<<"$longstring"sia ottimizzato per l'efficienza e con stringhe brevi, l'efficienza non importa molto comunque.
PSkocik,

È interessante notare che nel mio caso (su Ubuntu 14.04, Intel quad core i7) cat <(echo foo) >/dev/nullè più veloce di echo foo | cat >/dev/null.
pabouk,

1
@Prem Sì, sarebbe un approccio migliore, ma uno ancora migliore sarebbe non preoccuparsene affatto e usare lo strumento giusto per il lavoro. Non c'è motivo di pensare che gli heredoc siano ottimizzati per le prestazioni.
PSkocik,
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.