Utilisation des ressources à l'aide du canal et de la chaîne here

16

Nous pouvons obtenir le même résultat en utilisant les deux suivants dans bash,

echo 'foo' | cat

et

cat <<< 'foo'

Ma question est quelle est la différence entre ces deux en ce qui concerne les ressources utilisées et laquelle est la meilleure?

Ma pensée est que lors de l'utilisation de pipe, nous utilisons un processus supplémentaire echoet pipe alors que dans cette chaîne, seul un descripteur de fichier est utilisé cat.

utlamn
la source

Réponses:

17

Le canal est un fichier ouvert dans un système de fichiers dans le noyau et n'est pas accessible en tant que fichier normal sur le disque. Il est automatiquement mis en mémoire tampon uniquement jusqu'à une certaine taille et finira par se bloquer lorsqu'il sera plein. Contrairement aux fichiers provenant de blocs-périphériques, les canaux se comportent très comme des périphériques de caractères, et ne prennent donc généralement pas en charge lseek()et les données qui en sont lues ne peuvent pas être lues à nouveau comme vous le feriez avec un fichier normal.

La chaîne ici est un fichier normal créé dans un système de fichiers monté. Le shell crée le fichier et conserve son descripteur tout en supprimant immédiatement son seul lien vers le système de fichiers (et donc en le supprimant) avant qu'il n'écrive / ne lise un octet dans / depuis le fichier. Le noyau conservera l'espace requis pour le fichier jusqu'à ce que tous les processus libèrent tous les descripteurs pour celui-ci. Si l'enfant qui lit à partir d'un tel descripteur a la capacité de le faire, il peut être rembobiné lseek()et relu.

Dans les deux cas, les jetons <<<et |représentent les descripteurs de fichiers et pas nécessairement les fichiers eux-mêmes. Vous pouvez avoir une meilleure idée de ce qui se passe en faisant des choses comme:

readlink /dev/fd/1 | cat

...ou...

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

La différence la plus significative entre les deux fichiers est que la chaîne ici / doc est à peu près une affaire tout à la fois - le shell y écrit toutes les données avant d'offrir le descripteur de lecture à l'enfant. D'un autre côté, le shell ouvre le canal sur les descripteurs appropriés et bifurque les enfants pour gérer ceux du canal - et il est donc écrit / lu simultanément aux deux extrémités.

Ces distinctions, cependant, ne sont généralement vraies. Pour autant que je sache (ce qui n'est pas vraiment si loin), cela est vrai à peu près tous les shell qui traitent la <<<raccourci ici-chaîne pour <<une redirection ici-document à la seule exception de yash. yash, busybox, dashEt d' autres ashvariantes ont tendance à revenir ici-documents avec des tuyaux, cependant, et donc dans ces coquilles il y a vraiment très peu de différence entre les deux , après tout.

Ok - deux exceptions. Maintenant que j'y pense, ksh93ne fait pas du tout de tuyau |, mais gère plutôt toute l'entreprise avec des sockets - bien qu'il fasse un fichier tmp supprimé <<<*comme la plupart des autres. De plus, il ne place les sections séparées d'un pipeline dans un environnement de sous - shell qui est une sorte d'euphémisme POSIX car au moins il agit comme un sous-shell , et donc ne fait même pas les fourches.

Le fait est que les résultats de référence de @ PSkocik (ce qui est très utile) peuvent varier considérablement pour de nombreuses raisons, et la plupart d'entre eux dépendent de l'implémentation. Pour la configuration du document ici, les facteurs les plus importants seront le ${TMPDIR}type de système de fichiers cible et la configuration / disponibilité actuelle du cache, et encore plus la quantité de données à écrire. Pour le tuyau, ce sera la taille du processus shell lui-même, car des copies sont faites pour les fourches requises. De cette façon, bashc'est terrible lors de la configuration du pipeline (pour inclure les substitutions de $(commandes )) - car il est grand et très lent, mais ksh93cela ne fait pratiquement aucune différence.

Voici un autre petit extrait de shell pour montrer comment un shell se divise en sous-shell pour un 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 différence entre ce que pipe_who()rapporte un appel en pipeline et le rapport d'une exécution dans le shell actuel est due au comportement spécifié d' un (sous-shell )de réclamer le pid du shell parent $$lorsqu'il est développé. Bien que les bashsous- $$coquilles soient définitivement des processus distincts, le paramètre spécial de coquille n'est pas une source fiable de ces informations. Pourtant, le shell enfant du sous- shshell ne refuse pas de le signaler avec précision $PPID.

mikeserv
la source
Très utile. Le système de fichiers du noyau, y a-t-il un nom? cela signifie-t-il qu'il existe dans l'espace du noyau?
utlamn
2
@utlamn - en fait, oui - simplement des pipefs . Tout est dans le noyau - mais (à part des trucs comme FUSE), il en va de même pour toutes les E / S de fichiers .
mikeserv
10

Rien ne remplace le 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

Et pour une plus grande quantité de données:

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

Il semblerait que la version pipe ait un coût d'installation plus élevé mais est finalement plus efficace.

PSkocik
la source
@mikeserv C'était correct. J'ai ajouté une référence avec une plus grande quantité de données.
PSkocik
2
echo foo >/dev/shm/1;cat /dev/shm/1 >/dev/nullsemblait être rapide dans les deux cas ...
user23013
@ user23013 Cela a du sens. Je ne vois pas pourquoi soit echo "$longstring"ou <<<"$longstring"serait être modifié pour une efficacité et avec des chaînes courtes, l' efficacité n'a pas d' importance beaucoup de toute façon.
PSkocik
Il est intéressant de noter que dans mon cas (sur Ubuntu 14.04, Intel quad core i7) cat <(echo foo) >/dev/nullest plus rapide que echo foo | cat >/dev/null.
pabouk
1
@Prem Oui, ce serait une meilleure approche, mais une meilleure encore serait de ne pas s'inquiéter du tout et d'utiliser le bon outil pour le travail. Il n'y a aucune raison de penser que les heredocs seraient ajustés en fonction des performances.
PSkocik