Wie groß ist der Pipe Buffer?

Antworten:

142

Die Kapazität eines Pipe-Puffers variiert je nach System (und kann sogar auf demselben System variieren). Ich bin nicht sicher, ob es eine schnelle, einfache und plattformübergreifende Möglichkeit gibt, die Kapazität eines Rohrs zu ermitteln.

Mac OS X verwendet beispielsweise standardmäßig eine Kapazität von 16384 Byte, kann jedoch auf 65336 Byte umschalten, wenn große Schreibvorgänge in die Pipe ausgeführt werden, oder auf die Kapazität einer einzelnen Systemseite, wenn bereits zu viel Kernelspeicher vorhanden ist Wird von Pipe-Puffern verwendet (siehe xnu/bsd/sys/pipe.hund xnu/bsd/kern/sys_pipe.c; da diese von FreeBSD stammen, kann das gleiche Verhalten auch dort auftreten).

Eine Manpage zu Linux Pipe (7) besagt, dass die Pipe-Kapazität seit Linux 2.6.11 65536 Byte und eine einzelne Systemseite davor beträgt (z. B. 4096 Byte auf (32-Bit) x86-Systemen). Der Code ( include/linux/pipe_fs_i.h, und fs/pipe.c) scheint 16 System-Seiten zu verwenden (dh 64 KB, wenn eine System-Seite 4 KB groß ist), aber der Puffer für jede Pipe kann über eine Fcntl in der Pipe angepasst werden (bis zu einer maximalen Kapazität, die standardmäßig 1048576 beträgt) Bytes, kann aber über /proc/sys/fs/pipe-max-size)) geändert werden .


Hier ist eine kleine Bash / Perl- Kombination, mit der ich die Pipe-Kapazität auf meinem System getestet habe:

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

Ich habe festgestellt, dass es auf einem Mac OS X 10.6.7-System mit verschiedenen Schreibgrößen ausgeführt wird (beachten Sie die Änderung für Schreibvorgänge, die größer als 16 KB sind):

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

Dasselbe Skript unter 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

Hinweis: Der PIPE_BUFin den C-Header-Dateien definierte Wert (und der Pfadkonfigurationswert für _PC_PIPE_BUF) gibt nicht die Kapazität der Pipes an, sondern die maximale Anzahl von Bytes, die atomar geschrieben werden können (siehe POSIX- Schreibzugriff (2) ).

Zitat aus 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
quelle
14
Gute Antwort. Insbesondere für die Verknüpfung mit POSIX write (2) gilt: Die effektive Größe einer Pipe oder eines FIFO (die maximale Menge, die in einer Operation ohne Blockierung geschrieben werden kann) kann je nach Implementierung dynamisch variieren, sodass dies nicht möglich ist um einen festen Wert dafür anzugeben.
Mikel
5
Vielen Dank für die Erwähnung fcntl()unter Linux. Ich hatte eine Weile nach Pufferprogrammen für Benutzer gesucht, weil ich dachte, die eingebauten Pipes hätten nicht genügend Puffer. Jetzt sehe ich, dass dies der Fall ist, wenn ich CAP_SYS_RESOURCE habe oder root bereit ist, die maximale Pipe-Größe zu erweitern. Da das, was ich möchte, nur auf einem bestimmten Linux-Computer (meinem) ausgeführt wird, sollte dies kein Problem sein.
Daniel H
1
Können Sie bitte die Grundidee Ihres Skripts erläutern? Ich starre darauf und kann nicht herausfinden, wie es funktioniert? Vor allem, wozu verwenden Sie geschweifte Klammern hier VAR = $ ({})? Danke.
Wakan Tanka
@WakanTanka: Es ist ein bisschen viel in einem Kommentar zu beschreiben, aber dieses spezielle Konstrukt ist eine Parameterzuweisung ( var=…) der Ausgabe einer Befehlsersetzung ( $(…)), die gruppierte Befehle ( {…}, und (…)) enthält. Es werden auch mehrere (seltenere) Umleitungen (dh 0<&-und 3>&1) verwendet.
Chris Johnsen
2
@WakanTanka: Das Perl-Programm schreibt in Blöcken einer bestimmten Größe in seine stdout (eine von der Shell erstellte Pipe - die zu testende Pipe) und meldet seiner stderr die laufende Summe, wie viel geschrieben wurde (bis ein Fehler auftritt) - normalerweise, weil der Puffer der Pipe voll ist oder möglicherweise, weil das Leseende der Pipe nach kurzer Zeit geschlossen wurde ( exec 0<&-)). Der Abschlussbericht wird tail -1zusammen mit der Schreibgröße gesammelt ( ) und gedruckt.
Chris Johnsen
33

Diese Shell-Linie kann auch die Größe des Pipe-Puffers anzeigen:

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

(Senden von 1k Stücken an die blockierte Leitung, bis der Puffer voll ist) ... einige Testausgaben:

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

kürzester Bash-One-Liner mit printf:

M=0; while printf A; do >&2 printf "\r$((++M)) B"; done | sleep 999
Asain Kujovic
quelle
11
Sehr schön! (dd if=/dev/zero bs=1 | sleep 999) &warten dann eine zweite und killall -SIGUSR1 ddgibt 65536 bytes (66 kB) copied, 5.4987 s, 11.9 kB/s- wie Sie Ihre Lösung, aber bei 1 Byte Auflösung;)
Frostschutz
2
Unter Solaris 10/11 SPARC / x86 wird der ddBefehl mit 16 KB blockiert. Auf Fedora 23/25 x86-64 blockiert es bei 64 KiB.
Maxschlepzig
1
@frostschutz: Das ist eine schöne Vereinfachung. Pragmatisch könnte man einfach dd if=/dev/zero bs=1 | sleep 999in den Vordergrund rennen , eine Sekunde warten und dann drücken ^C. Wenn Sie einen killalldd if=/dev/zero bs=1 | sleep 999 & sleep 1 && pkill -INT -P $$ -x dd
Einzeiler
7

Hier sind einige weitere Alternativen, um die tatsächliche Pipe-Pufferkapazität nur mit Shell-Befehlen zu ermitteln:

# 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
quelle
Unter Solaris 10 wird getconf PIPE_BUF /gedruckt, 5120was mit der ulimit -a | grep pipeAusgabe übereinstimmt, aber nicht mit den 16 KB, nach denen dd .. | sleep ...blockiert wird.
Maxschlepzig
Auf Fedora 25 wird Ihre erste yesMethode 73728anstelle der mitdd if=/dev/zero bs=4096 status=none | pv -bn | sleep 1
maxschlepzig
6

Dies ist ein schneller und schmutziger Hack auf 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
quelle
0
$ ulimit -a | grep pipe
pipe size            (512 bytes, -p) 8

Also habe ich auf meiner Linux-Box standardmäßig 8 * 512 = 4096-Byte-Pipes.

Solaris und viele andere Systeme haben eine ähnliche ulimit-Funktion.

Sam Watkins
quelle
2
Diese Ausgabe wird (512 bytes, -p) 8unter Fedora 23/25 und 512 bytes, -p) 10Solaris 10 gedruckt. Diese Werte stimmen nicht mit den Puffergrößen überein, die experimentell mit einer Blockierung ermittelt wurden dd.
Maxschlepzig
0

Wenn Sie den Wert in Python> = 3.3 benötigen, ist dies eine einfache Methode (vorausgesetzt, Sie können call out to ausführen 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()))
unhammer
quelle