Quelle est la taille du tampon de tuyau?

Réponses:

142

La capacité d'un tampon de tuyauterie varie d'un système à l'autre (et peut même varier sur le même système). Je ne suis pas sûr qu'il existe un moyen rapide, simple et multiplateforme de simplement rechercher la capacité d'un tuyau.

Mac OS X, par exemple, utilise une capacité de 16 384 octets par défaut, mais peut passer à une capacité de 65 336 octets si une écriture de grande taille est effectuée dans le canal, ou passera à une capacité d'une seule page système si la mémoire du noyau est déjà trop importante. utilisé par les tampons de pipe (voir xnu/bsd/sys/pipe.h, et xnu/bsd/kern/sys_pipe.c; puisque ceux-ci proviennent de FreeBSD, le même comportement peut se produire là aussi).

Une page de manuel pipe (7) de Linux indique que la capacité de la conduite est de 65 536 octets depuis Linux 2.6.11 et d'une seule page système antérieure à celle-ci (par exemple, 4096 octets sur des systèmes x86 (32 bits)). Le code ( include/linux/pipe_fs_i.h, et fs/pipe.c) semble utiliser 16 pages système (c’est-à-dire 64 Ko si une page système est de 4 Ko), mais le tampon de chaque conduite peut être ajusté via un fcntl sur la conduite (jusqu’à une capacité maximale de 1048576 par défaut) octets, mais peut être modifié via /proc/sys/fs/pipe-max-size)).


Voici une petite combinaison bash / perl que j'ai utilisée pour tester la capacité de conduite de mon système:

#!/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"

Voici ce que j'ai trouvé en l'exécutant avec différentes tailles d'écriture sur un système Mac OS X 10.6.7 (notez le changement pour les écritures supérieures à 16 Ko):

% /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

Le même script sous 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

Remarque: La PIPE_BUFvaleur définie dans les fichiers d'en-tête C (et la valeur de pathconf pour _PC_PIPE_BUF) ne spécifie pas la capacité des canaux, mais le nombre maximal d'octets pouvant être écrits de manière atomique (voir POSIX write (2) ).

Citation 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
la source
14
Très bonne réponse. En particulier pour le lien vers POSIX write (2), qui indique: La taille effective d'un tube ou d'une FIFO (la quantité maximale pouvant être écrite en une opération sans blocage) peut varier de manière dynamique, en fonction de la mise en œuvre, il est donc impossible pour spécifier une valeur fixe pour cela.
Mikel
5
Merci de mentionner fcntl()sur Linux; J'avais passé un certain temps à rechercher des programmes de mise en mémoire tampon de l'espace utilisateur, car je pensais que les canaux intégrés ne disposaient pas d'une mémoire tampon suffisamment grande. Maintenant, je vois qu'ils le font, si j'ai CAP_SYS_RESOURCE ou si root est prêt à augmenter la taille maximale du tuyau. Comme ce que je veux ne sera exécuté que sur un ordinateur Linux spécifique (le mien), cela ne devrait pas poser de problème.
Daniel H
1
Pouvez-vous s'il vous plaît expliquer l'idée de base de votre script? Je le regarde et je ne peux pas comprendre comment ça marche? Surtout à quoi servent les accolades ici VAR = $ ({})? Je vous remercie.
Wakan Tanka
@WakanTanka: C'est un peu difficile à décrire dans un commentaire, mais cette construction particulière est un paramètre assignation ( var=…) de la sortie d'une commande substitution ( $(…)) qui inclut des commandes groupées ( {…}, et (…)). Il utilise également plusieurs redirections ( moins communes) (ie 0<&-et 3>&1).
Chris Johnsen
2
@WakanTanka: le programme Perl écrit sur son stdout (un tuyau créé par le shell, celui qui est testé) par blocs d'une taille donnée et rapporte à son stderr le total cumulé de son contenu écrit (jusqu'à l'obtention d'une erreur) - généralement parce que le tampon du tuyau est plein ou peut-être parce que l'extrémité de lecture du tuyau a été fermée après un court laps de temps ( exec 0<&-)). Le rapport final est collecté ( tail -1) et imprimé avec la taille d'écriture.
Chris Johnsen
33

cette ligne de commande peut aussi montrer la taille du tampon de tuyau:

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

(envoi de 1 000 morceaux au tuyau bloqué jusqu’à saturation du tampon) ... certaines sorties de test:

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

le plus court bash-one-liner utilisant printf:

M=0; while printf A; do >&2 printf "\r$((++M)) B"; done | sleep 999
Asain Kujovic
la source
11
Très agréable! (dd if=/dev/zero bs=1 | sleep 999) &puis attendez une seconde et killall -SIGUSR1 dddonne 65536 bytes (66 kB) copied, 5.4987 s, 11.9 kB/s- identique à votre solution, mais à une résolution de 1 octet;)
frostschutz
2
Pour mémoire, sous Solaris 10/11 SPARC / x86, la ddcommande est bloquée à 16 Ko. Sur Fedora 23/25 x86-64, il bloque à 64 Ko.
maxschlepzig
1
@frostschutz: C'est une belle simplification. De manière pragmatique, vous pouvez simplement courir dd if=/dev/zero bs=1 | sleep 999au premier plan, attendre une seconde, puis appuyer sur ^C. Si vous vouliez un one-liner sur Linux et BSD / macOS (plus robuste que l’utilisation killall):dd if=/dev/zero bs=1 | sleep 999 & sleep 1 && pkill -INT -P $$ -x dd
mklement0
7

Voici d'autres alternatives pour explorer la capacité réelle du tampon de pipe à l'aide de commandes shell uniquement:

# 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
la source
Sous Solaris 10, les getconf PIPE_BUF /impressions 5120correspondent à la ulimit -a | grep pipesortie mais ne correspondent pas aux 16 Ko après lesquels des dd .. | sleep ...blocs.
maxschlepzig
Sur Fedora 25, votre première yesméthode imprime 73728au lieu des 64 Ko déterminés avecdd if=/dev/zero bs=4096 status=none | pv -bn | sleep 1
maxschlepzig le
6

Ceci est un hack rapide et sale sur 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
la source
0
$ ulimit -a | grep pipe
pipe size            (512 bytes, -p) 8

Donc, sur ma machine Linux, j'ai 8 * 512 = 4096 octets de canaux par défaut.

Solaris et de nombreux autres systèmes ont une fonction ulimit similaire.

Sam Watkins
la source
2
Cela s’imprime (512 bytes, -p) 8sur Fedora 23/25 et 512 bytes, -p) 10sur Solaris 10 - et ces valeurs ne correspondent pas aux tailles de mémoire tampon déduites de manière expérimentale avec un blocage dd.
maxschlepzig
0

Si vous avez besoin de la valeur dans Python> = 3.3, voici une méthode simple (en supposant que vous puissiez exécuter call out 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()))
sans retenue
la source