Uso de recursos usando tubería y aquí cadena

16

Podemos obtener el mismo resultado usando los dos siguientes bash,

echo 'foo' | cat

y

cat <<< 'foo'

Mi pregunta es ¿cuál es la diferencia entre estos dos en lo que respecta a los recursos utilizados y cuál es mejor?

Mi pensamiento es que mientras usamos pipe estamos usando un proceso extra echoy pipe mientras que aquí solo se usa un descriptor de archivo cat.

utlamn
fuente

Respuestas:

17

La canalización es un archivo abierto en un sistema de archivos en el núcleo y no es accesible como un archivo normal en el disco. Se almacena automáticamente solo en un cierto tamaño y eventualmente se bloqueará cuando esté lleno. A diferencia de los archivos originados en dispositivos de bloque, las canalizaciones se comportan de manera muy similar a los dispositivos de caracteres, por lo que generalmente no son compatibles lseek()y los datos que se leen de ellos no se pueden volver a leer como lo haría con un archivo normal.

La cadena aquí es un archivo normal creado en un sistema de archivos montado. El shell crea el archivo y retiene su descriptor mientras elimina inmediatamente su único enlace al sistema de archivos (y así lo elimina) antes de escribir / leer un byte en / desde el archivo. El kernel mantendrá el espacio requerido para el archivo hasta que todos los procesos liberen todos los descriptores. Si el niño que lee de tal descriptor tiene la capacidad de hacerlo, puede rebobinarlo lseek()y leerlo nuevamente.

En ambos casos, los tokens <<<y |los descriptores de archivo representan no necesariamente los archivos en sí. Puede obtener una mejor idea de lo que está sucediendo haciendo cosas como:

readlink /dev/fd/1 | cat

...o...

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

La diferencia más significativa entre los dos archivos es que here-string / doc es prácticamente un asunto de una sola vez: el shell escribe todos los datos en él antes de ofrecer el descriptor de lectura al niño. Por otro lado, el shell abre la tubería en los descriptores apropiados y bifurca a los niños para administrarlos para la tubería, por lo que se escribe / lee simultáneamente en ambos extremos.

Sin embargo, estas distinciones solo son generalmente ciertas. Hasta donde yo sé (que en realidad no es tan lejos), esto es cierto para casi todos los shell que manejan la <<<abreviatura here-string para <<una redirección here-document con la única excepción de yash. yash, busybox, dash, Y otras ashvariantes tienden a respaldar los documentos internos de tuberías, sin embargo, y por lo que en esas conchas realmente hay muy poca diferencia entre los dos, después de todo.

Ok, dos excepciones. Ahora que lo estoy pensando, en ksh93realidad no sirve para nada |, sino que maneja todo el negocio con sockets, aunque sí hace un archivo tmp eliminado para la <<<*mayoría de los demás. Además, solo coloca las secciones separadas de una tubería en un entorno de subshell que es una especie de eufemismo POSIX porque al menos actúa como un subshell y, por lo tanto, ni siquiera hace las horquillas.

El hecho es que los resultados de referencia de @ PSkocik (que es muy útil) aquí pueden variar ampliamente por muchas razones, y la mayoría de estos dependen de la implementación. Para la configuración del documento aquí, los factores más importantes serán el ${TMPDIR}tipo de sistema de archivos objetivo y la configuración / disponibilidad de caché actual, y aún más la cantidad de datos que se escribirán. Para la tubería, será del tamaño del proceso de shell en sí, porque se hacen copias para las horquillas requeridas. De esta manera, bashes terrible en la configuración de la canalización (para incluir sustituciones de $(comandos )) , porque es grande y muy lento, pero ksh93casi no hace ninguna diferencia.

Aquí hay otro pequeño fragmento de shell para demostrar cómo un shell se divide de los subshell para una tubería:

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 diferencia entre lo que pipe_who()informa una llamada canalizada y el informe de una ejecución en el shell actual se debe al comportamiento especificado de un (subshell )de reclamar el pid del shell padre $$cuando se expande. Aunque los bashsubsales definitivamente son procesos separados, el $$parámetro especial del shell no es una fuente confiable de esta información. Aún así, el shshell secundario del subshell no se niega a informar con precisión su $PPID.

mikeserv
fuente
Muy útil. El sistema de archivos en el núcleo, ¿hay un nombre para él? ¿significa que existe en el espacio del kernel?
utlamn
2
@utlamn, en realidad sí, simplemente pipefs . Es todo en el núcleo - pero (aparte de cosas como FUSE) por lo que es todo el archivo de E / S .
mikeserv
10

No hay sustituto para la evaluación comparativa:

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

Y para una mayor cantidad de datos:

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

Parece que la versión de tubería tiene un costo de configuración mayor pero al final es más eficiente.

PSkocik
fuente
@mikeserv Eso fue correcto. Agregué un punto de referencia con una mayor cantidad de datos.
PSkocik
2
echo foo >/dev/shm/1;cat /dev/shm/1 >/dev/nullparecía ser rápido en ambos casos ...
user23013
@ user23013 Eso tiene sentido. No veo por qué tampoco echo "$longstring"o <<<"$longstring"sería ajustado para la eficiencia y con cadenas cortas, la eficiencia no importa mucho de todos modos.
PSkocik
Es interesante que en mi caso (en Ubuntu 14.04, Intel quad core i7) cat <(echo foo) >/dev/nullsea ​​más rápido que echo foo | cat >/dev/null.
pabouk
1
@Prem Sí, ese sería un mejor enfoque, pero uno aún mejor sería no preocuparse por esto en absoluto y usar la herramienta adecuada para el trabajo. No hay razón para pensar que los documentos aquí estarían ajustados al rendimiento.
PSkocik