¿Qué tan grande es el buffer de la tubería?

Respuestas:

142

La capacidad de un buffer de tubería varía entre sistemas (e incluso puede variar en el mismo sistema). No estoy seguro de que haya una manera rápida, fácil y multiplataforma para buscar la capacidad de una tubería.

Mac OS X, por ejemplo, usa una capacidad de 16384 bytes de manera predeterminada, pero puede cambiar a capacidades de 65336 bytes si se realiza una gran escritura en la tubería, o cambiará a una capacidad de una sola página del sistema si ya hay demasiada memoria del núcleo siendo utilizado por los buffers de tubería (ver xnu/bsd/sys/pipe.h, y xnu/bsd/kern/sys_pipe.c; dado que estos son de FreeBSD, el mismo comportamiento también puede ocurrir allí).

Una página del comando man Linux pipe (7) dice que la capacidad de la tubería es 65536 bytes desde Linux 2.6.11 y una sola página del sistema anterior (por ejemplo, 4096 bytes en sistemas x86 (32 bits)). El código ( include/linux/pipe_fs_i.hy fs/pipe.c) parece usar 16 páginas del sistema (es decir, 64 KiB si una página del sistema es 4 KiB), pero el búfer para cada tubería se puede ajustar a través de un fcntl en la tubería (hasta una capacidad máxima que por defecto es 1048576 bytes, pero se puede cambiar a través de /proc/sys/fs/pipe-max-size)).


Aquí hay una pequeña combinación bash / perl que utilicé para probar la capacidad de la tubería en mi sistema:

#!/bin/bash
test $# -ge 1 || { echo "usage: $0 write-size [wait-time]"; exit 1; }
test $# -ge 2 || set -- "$@" 1
bytes_written=$(
{
    exec 3>&1
    {
        perl -e '
            $size = $ARGV[0];
            $block = q(a) x $size;
            $num_written = 0;
            sub report { print STDERR $num_written * $size, qq(\n); }
            report; while (defined syswrite STDOUT, $block) {
                $num_written++; report;
            }
        ' "$1" 2>&3
    } | (sleep "$2"; exec 0<&-);
} | tail -1
)
printf "write size: %10d; bytes successfully before error: %d\n" \
    "$1" "$bytes_written"

Esto es lo que encontré ejecutándolo con varios tamaños de escritura en un sistema Mac OS X 10.6.7 (tenga en cuenta el cambio para escrituras de más de 16 KB):

% /bin/bash -c 'for p in {0..18}; do /tmp/ts.sh $((2 ** $p)) 0.5; done'
write size:          1; bytes successfully before error: 16384
write size:          2; bytes successfully before error: 16384
write size:          4; bytes successfully before error: 16384
write size:          8; bytes successfully before error: 16384
write size:         16; bytes successfully before error: 16384
write size:         32; bytes successfully before error: 16384
write size:         64; bytes successfully before error: 16384
write size:        128; bytes successfully before error: 16384
write size:        256; bytes successfully before error: 16384
write size:        512; bytes successfully before error: 16384
write size:       1024; bytes successfully before error: 16384
write size:       2048; bytes successfully before error: 16384
write size:       4096; bytes successfully before error: 16384
write size:       8192; bytes successfully before error: 16384
write size:      16384; bytes successfully before error: 16384
write size:      32768; bytes successfully before error: 65536
write size:      65536; bytes successfully before error: 65536
write size:     131072; bytes successfully before error: 0
write size:     262144; bytes successfully before error: 0

El mismo script en Linux 3.19:

/bin/bash -c 'for p in {0..18}; do /tmp/ts.sh $((2 ** $p)) 0.5; done'
write size:          1; bytes successfully before error: 65536
write size:          2; bytes successfully before error: 65536
write size:          4; bytes successfully before error: 65536
write size:          8; bytes successfully before error: 65536
write size:         16; bytes successfully before error: 65536
write size:         32; bytes successfully before error: 65536
write size:         64; bytes successfully before error: 65536
write size:        128; bytes successfully before error: 65536
write size:        256; bytes successfully before error: 65536
write size:        512; bytes successfully before error: 65536
write size:       1024; bytes successfully before error: 65536
write size:       2048; bytes successfully before error: 65536
write size:       4096; bytes successfully before error: 65536
write size:       8192; bytes successfully before error: 65536
write size:      16384; bytes successfully before error: 65536
write size:      32768; bytes successfully before error: 65536
write size:      65536; bytes successfully before error: 65536
write size:     131072; bytes successfully before error: 0
write size:     262144; bytes successfully before error: 0

Nota: El PIPE_BUFvalor definido en los archivos de encabezado C (y el valor de pathconf para _PC_PIPE_BUF) no especifica la capacidad de las canalizaciones, sino el número máximo de bytes que pueden escribirse atómicamente (consulte POSIX write (2) ).

Cita de include/linux/pipe_fs_i.h:

/* Differs from PIPE_BUF in that PIPE_SIZE is the length of the actual
   memory allocation, whereas PIPE_BUF makes atomicity guarantees.  */
Chris Johnsen
fuente
14
Gran respuesta. Especialmente para el enlace a POSIX write (2), que dice: El tamaño efectivo de una tubería o FIFO (la cantidad máxima que se puede escribir en una operación sin bloqueo) puede variar dinámicamente, dependiendo de la implementación, por lo que no es posible para especificar un valor fijo para ello.
Mikel
55
Gracias por mencionar fcntl()en Linux; Pasé un tiempo buscando programas de almacenamiento en búfer del espacio de usuario porque pensé que las tuberías incorporadas no tenían un búfer lo suficientemente grande. Ahora veo que sí, si tengo CAP_SYS_RESOURCE o root está dispuesto a expandir el tamaño máximo de tubería. Como lo que quiero solo se ejecutará en una computadora Linux específica (la mía), esto no debería ser un problema.
Daniel H
1
¿Puedes explicar la idea básica de tu guión? Lo estoy mirando y no puedo entender cómo funciona. ¿Especialmente cuál es el propósito de usar llaves aquí VAR = $ ({})? Gracias.
Wakan Tanka
@WakanTanka: es un poco demasiado para describir en un comentario, pero esa construcción particular es una asignación de parámetros ( var=…) de la salida de una sustitución de comando ( $(…)) que incluye comandos agrupados ( {…}, y (…)). También utiliza varias redirecciones ( menos comunes) (es decir, 0<&-y 3>&1).
Chris Johnsen
2
@WakanTanka: el programa Perl escribe en su stdout (una tubería creada por shell, la que se está probando) en bloques de un tamaño determinado e informa a su stderr un total acumulado de cuánto ha escrito (hasta que se produce un error —Por lo general porque el búfer de la tubería está lleno o posiblemente porque el extremo de lectura de la tubería se ha cerrado después de un corto tiempo ( exec 0<&-)). El informe final se recopila ( tail -1) y se imprime junto con el tamaño de escritura.
Chris Johnsen
33

esta línea de shell también puede mostrar el tamaño del buffer de tubería:

M=0; while true; do dd if=/dev/zero bs=1k count=1 2>/dev/null; \
       M=$(($M+1)); echo -en "\r$M KB" 1>&2; done | sleep 999

(enviando trozos de 1k a la tubería bloqueada hasta que el buffer esté lleno) ... algunas salidas de prueba:

64K (intel-debian), 32K (aix-ppc), 64K (jslinux bellard.org)      ...Ctrl+C.

el más corto bash-one-liner usando printf:

M=0; while printf A; do >&2 printf "\r$((++M)) B"; done | sleep 999
Asain Kujovic
fuente
11
¡Muy agradable! (dd if=/dev/zero bs=1 | sleep 999) &luego espera un segundo y killall -SIGUSR1 ddda 65536 bytes (66 kB) copied, 5.4987 s, 11.9 kB/s, igual que tu solución, pero con una resolución de 1 byte;)
frostschutz
2
Para el registro, en Solaris 10/11 SPARC / x86, el ddcomando se bloquea a 16 KiB. En Fedora 23/25 x86-64, se bloquea a 64 KiB.
maxschlepzig
1
@frostschutz: Esa es una buena simplificación. Pragmáticamente, puedes correr dd if=/dev/zero bs=1 | sleep 999en primer plano, esperar un segundo y luego presionar ^C. Si quería una línea en Linux y BSD / macOS (más robusto que usar killall):dd if=/dev/zero bs=1 | sleep 999 & sleep 1 && pkill -INT -P $$ -x dd
mklement0
7

Aquí hay algunas alternativas adicionales para explorar la capacidad real del búfer de tubería utilizando solo comandos de shell:

# get pipe buffer size using Bash
yes produce_this_string_as_output | tee >(sleep 1) | wc -c

# portable version
( (sleep 1; exec yes produce_this_string_as_output) & echo $! ) | 
     (pid=$(head -1); sleep 2; kill "$pid"; wc -c </dev/stdin)

# get buffer size of named pipe
sh -c '
  rm -f fifo
  mkfifo fifo
  yes produce_this_string_as_output | tee fifo | wc -c &
  exec 3<&- 3<fifo
  sleep 1
  exec 3<&-
  rm -f fifo
'

# Mac OS X
#getconf PIPE_BUF /
#open -e /usr/include/limits.h /usr/include/sys/pipe.h
# PIPE_SIZE
# BIG_PIPE_SIZE
# SMALL_PIPE_SIZE
# PIPE_MINDIRECT
chan
fuente
En Solaris 10, getconf PIPE_BUF /imprime lo 5120que coincide con la ulimit -a | grep pipesalida pero no con los 16 KiB después de los cuales se dd .. | sleep ...bloquea.
maxschlepzig
En Fedora 25, su primer yesmétodo se imprime en 73728lugar del 64 KiB determinado condd if=/dev/zero bs=4096 status=none | pv -bn | sleep 1
maxschlepzig
6

Este es un truco rápido y sucio en Ubuntu 12.04, YMMV

cat >pipesize.c

#include <unistd.h>
#include <errno.h>
#include </usr/include/linux/fcntl.h>
#include <stdio.h>

void main( int argc, char *argv[] ){
  int fd ;
  long pipesize ;

  if( argc>1 ){
  // if command line arg, associate a file descriptor with it
    fprintf( stderr, "sizing %s ... ", argv[1] );
    fd = open( argv[1], O_RDONLY|O_NONBLOCK );
  }else{
  // else use STDIN as the file descriptor
    fprintf( stderr, "sizing STDIN ... " );
    fd = 0 ;
  }

  fprintf( stderr, "%ld bytes\n", (long)fcntl( fd, F_GETPIPE_SZ ));
  if( errno )fprintf( stderr, "Uh oh, errno is %d\n", errno );
  if( fd )close( fd );
}

gcc -o pipesize pipesize.c

mkfifo /tmp/foo

./pipesize /tmp/foo

>sizing /tmp/foo ... 65536 bytes

date | ./pipesize

>sizing STDIN ... 65536 bytes
Jeff
fuente
0
$ ulimit -a | grep pipe
pipe size            (512 bytes, -p) 8

Entonces, en mi caja de Linux tengo 8 * 512 = 4096 bytes de tuberías por defecto.

Solaris y muchos otros sistemas tienen una función ulimit similar.

Sam Watkins
fuente
2
Esto se imprime (512 bytes, -p) 8en Fedora 23/25 y 512 bytes, -p) 10en Solaris 10, y esos valores no coinciden con los tamaños de búfer derivados experimentalmente con un bloqueo dd.
maxschlepzig
0

Si necesita el valor en Python> = 3.3, aquí hay un método simple (suponiendo que pueda ejecutar call to dd):

from subprocess import Popen, PIPE, TimeoutExpired
p = Popen(["dd", "if=/dev/zero", "bs=1"], stdin=PIPE, stdout=PIPE)
try: 
    p.wait(timeout=1)
except TimeoutExpired: 
    p.kill()
    print(len(p.stdout.read()))
desarmar
fuente