Quanto è grande il buffer del tubo?


Risposte:


142

La capacità di un buffer di tubi varia tra i sistemi (e può anche variare sullo stesso sistema). Non sono sicuro che esista un modo rapido, semplice e multipiattaforma per cercare la capacità di un tubo.

Mac OS X, ad esempio, utilizza una capacità di 16384 byte per impostazione predefinita, ma può passare a capacità 65336 byte se viene effettuata una scrittura di grandi dimensioni sulla pipe o passerà a una capacità di una singola pagina di sistema se è già presente troppa memoria del kernel usato dai buffer delle pipe (vedi xnu/bsd/sys/pipe.h, e xnu/bsd/kern/sys_pipe.cpoiché questi sono di FreeBSD, lo stesso comportamento può accadere anche lì).

Una pagina man di pipe Linux (7) dice che la capacità di pipe è 65536 byte a partire da Linux 2.6.11 e una singola pagina di sistema precedente (ad esempio 4096 byte su sistemi x86 (32 bit)). Il codice ( include/linux/pipe_fs_i.h, e fs/pipe.c) sembra utilizzare 16 pagine di sistema (ovvero 64 KiB se una pagina di sistema è 4 KiB), ma il buffer per ogni pipe può essere regolato tramite un fcntl sulla pipe (fino a una capacità massima che viene impostata automaticamente su 1048576 byte, ma può essere modificato tramite /proc/sys/fs/pipe-max-size)).


Ecco una piccola combinazione bash / perl che ho usato per testare la capacità del tubo sul mio 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"

Ecco cosa ho trovato eseguendolo con varie dimensioni di scrittura su un sistema Mac OS X 10.6.7 (nota la modifica per scritture di dimensioni superiori a 16 KiB):

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

Lo stesso script su 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: il PIPE_BUFvalore definito nei file di intestazione C (e il valore pathconf per _PC_PIPE_BUF), non specifica la capacità delle pipe, ma il numero massimo di byte che possono essere scritti atomicamente (vedere POSIX write (2) ).

Citazione da 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.  */

14
Bella risposta. Soprattutto per il collegamento a POSIX write (2), che dice: La dimensione effettiva di una pipe o FIFO (la quantità massima che può essere scritta in una operazione senza blocco) può variare dinamicamente, a seconda dell'implementazione, quindi non è possibile per specificare un valore fisso per esso.
Mikel,

5
Grazie per aver parlato di fcntl()Linux; Ho trascorso un po 'a cercare i programmi di buffering dello spazio utente perché pensavo che le pipe integrate non avessero un buffer abbastanza grande. Ora vedo che lo fanno, se ho CAP_SYS_RESOURCE o root è disposto ad espandere la dimensione massima del tubo. Poiché ciò che voglio verrà eseguito solo su un computer Linux specifico (il mio), questo non dovrebbe essere un problema.
Daniel H,

1
Puoi per favore spiegare l'idea di base della tua sceneggiatura? Lo sto fissando e non riesco a capire come funziona? Soprattutto qual è lo scopo usando le parentesi graffe qui VAR = $ ({})? Grazie.
Wakan Tanka,

@WakanTanka: è un po 'troppo da descrivere in un commento, ma quel particolare costrutto è un'assegnazione di parametri ( var=…) dell'output di una sostituzione di comando ( $(…)) che include comandi raggruppati ( {…}, e (…)). Utilizza anche diversi reindirizzamenti ( meno comuni) (ie 0<&-e 3>&1).
Chris Johnsen,

2
@WakanTanka: il programma Perl scrive sul suo stdout (una pipe creata dalla shell — quella che viene testata) in blocchi di una data dimensione e riporta al suo stderr un totale parziale di quanto ha scritto (fino a quando non viene visualizzato un errore —Usualmente perché il buffer della pipe è pieno o forse perché la fine della lettura della pipe è stata chiusa dopo poco tempo ( exec 0<&-)). Il rapporto finale viene raccolto ( tail -1) e stampato insieme alle dimensioni di scrittura.
Chris Johnsen,

33

questa shell-line può mostrare anche la dimensione del buffer di pipe:

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

(invio di blocchi da 1k alla pipe bloccata fino al buffer pieno) ... alcuni output di test:

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

bash-one-liner più corto con printf:

M=0; while printf A; do >&2 printf "\r$((++M)) B"; done | sleep 999

11
Molto bella! (dd if=/dev/zero bs=1 | sleep 999) &quindi aspetta un secondo e killall -SIGUSR1 dd65536 bytes (66 kB) copied, 5.4987 s, 11.9 kB/s- lo stesso della tua soluzione, ma alla risoluzione di 1 byte;)
frostschutz

2
Per la cronaca, su Solaris 10/11 SPARC / x86 il ddcomando si blocca a 16 KiB. Su Fedora 23/25 x86-64, si blocca a 64 KiB.
maxschlepzig,

1
@frostschutz: è una bella semplificazione. Pragmaticamente, potresti semplicemente correre dd if=/dev/zero bs=1 | sleep 999in primo piano, attendere un secondo, quindi premere ^C. Se volevi un one-liner su Linux e BSD / macOS (più robusto dell'uso killall):dd if=/dev/zero bs=1 | sleep 999 & sleep 1 && pkill -INT -P $$ -x dd
mklement0

7

Ecco alcune ulteriori alternative per esplorare la capacità effettiva del buffer di pipe utilizzando solo i comandi 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

Su Solaris 10, getconf PIPE_BUF /stampa 5120che corrisponde ulimit -a | grep pipeall'output ma non corrisponde al 16 KiB, dopodiché si dd .. | sleep ...blocca.
maxschlepzig

Su Fedora 25, il tuo primo yesmetodo stampa 73728invece del 64 KiB determinato condd if=/dev/zero bs=4096 status=none | pv -bn | sleep 1
maxschlepzig

6

Questo è un trucco rapido e sporco su 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

0
$ ulimit -a | grep pipe
pipe size            (512 bytes, -p) 8

Quindi sulla mia scatola di Linux ho 8 * 512 = 4096 byte di default.

Solaris e molti altri sistemi hanno una funzione ulimit simile.


2
Questo stampa (512 bytes, -p) 8su Fedora 23/25 e 512 bytes, -p) 10su Solaris 10 - e quei valori non corrispondono alle dimensioni del buffer derivate sperimentalmente con un blocco dd.
maxschlepzig

0

Se hai bisogno del valore in Python> = 3.3, ecco un metodo semplice (supponendo che tu possa eseguire la chiamata a 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()))
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.