Jak duży jest bufor rur?

146

Jako komentarz w nie jestem pewien, dlaczego „| true” w pliku makefile ma taki sam efekt, jak napisany przez użytkownika „|| true” użytkownik cjm :

Kolejny powód do uniknięcia prawda jest taka, że ​​jeśli polecenie wytworzyło wystarczającą ilość danych wyjściowych do wypełnienia bufora potoku, zablokowałoby oczekiwanie na przeczytanie go przez true.

Czy mamy jakiś sposób, aby dowiedzieć się, jaki jest rozmiar bufora potoku?

Kit Sunde
źródło

Odpowiedzi:

142

Pojemność bufora potoku różni się w zależności od systemu (i może nawet różnić się w tym samym systemie). Nie jestem pewien, czy istnieje szybki, łatwy i wieloplatformowy sposób, aby po prostu sprawdzić pojemność rury.

Na przykład Mac OS X domyślnie wykorzystuje pojemność 16384 bajtów, ale może przełączyć się na pojemność 65336 bajtów, jeśli do potoku wprowadzono duży zapis, lub przełączy się na pojemność pojedynczej strony systemowej, jeśli już jest za dużo pamięci jądra używane przez bufory potokowe (patrz xnu/bsd/sys/pipe.hi xnu/bsd/kern/sys_pipe.c; ponieważ pochodzą one z FreeBSD, to samo może się tam zdarzyć).

Jedna strona podręcznika systemowego potoku Linux (7) mówi, że pojemność potoku wynosi 65536 bajtów od Linuksa 2.6.11, a wcześniej jedna strona systemowa (np. 4096 bajtów w systemach (32-bitowych) x86). Wydaje się, że kod ( include/linux/pipe_fs_i.hi fs/pipe.c) używa 16 stron systemowych (tj. 64 KiB, jeśli strona systemowa ma 4 KiB), ale bufor dla każdej potoki można regulować za pomocą fcntl na potoku (do maksymalnej pojemności, która domyślnie wynosi 1048576 bajtów, ale można je zmienić za pomocą /proc/sys/fs/pipe-max-size)).


Oto mała kombinacja bash / perl , której użyłem do przetestowania pojemności rur w moim systemie:

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

Oto, co znalazłem, uruchamiając go z różnymi rozmiarami zapisu w systemie Mac OS X 10.6.7 (zwróć uwagę na zmianę zapisu większą niż 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

Ten sam skrypt w systemie 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

Uwaga: PIPE_BUFWartość zdefiniowana w plikach nagłówka C (i wartość pathconf dla _PC_PIPE_BUF) nie określa pojemności potoków, ale maksymalną liczbę bajtów, które można zapisać atomowo (patrz zapis POSIX (2) ).

Cytat z 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
źródło
14
Świetna odpowiedź. Szczególnie w przypadku linku do zapisu POSIX (2), który mówi: Efektywny rozmiar potoku lub FIFO (maksymalna ilość, którą można zapisać w jednej operacji bez blokowania) może zmieniać się dynamicznie, w zależności od implementacji, więc nie jest to możliwe określić dla niego stałą wartość.
Mikel
5
Dzięki za wzmiankę fcntl()o Linuksie; Spędziłem trochę czasu na poszukiwaniu programów buforujących przestrzeń użytkownika, ponieważ myślałem, że wbudowane potoki nie mają wystarczająco dużego bufora. Teraz widzę, że tak, jeśli mam CAP_SYS_RESOURCE lub root chce zwiększyć maksymalny rozmiar rury. Ponieważ to, czego chcę, będzie działało tylko na określonym komputerze z Linuksem (moim), nie powinno to stanowić problemu.
Daniel H
1
Czy możesz wyjaśnić podstawową ideę swojego skryptu? Patrzę na to i nie mogę zrozumieć, jak to działa? Szczególnie jaki jest cel używania nawiasów klamrowych tutaj VAR = $ ({})? Dziękuję Ci.
Wakan Tanka,
@WakanTanka: W komentarzu można opisać nieco więcej, ale ten konkretny konstrukt jest przypisaniem parametru ( var=…) wyniku funkcji podstawiania poleceń ( $(…)), która obejmuje polecenia zgrupowane ( {…}, i (…)). Wykorzystuje także kilka (mniej powszechnych) przekierowań (tj. 0<&-I 3>&1).
Chris Johnsen
2
@WakanTanka: Program Perl zapisuje na swoim wyjściu (potok utworzony przez powłokę - ten, który jest testowany) w blokach o danym rozmiarze i zgłasza swojemu stderr bieżącą sumę, ile napisał (dopóki nie pojawi się błąd - zwykle dlatego, że bufor potoku jest pełny lub być może dlatego, że koniec odczytu potoku został zamknięty po krótkim czasie ( exec 0<&-)). Raport końcowy jest zbierany ( tail -1) i drukowany wraz z rozmiarem zapisu.
Chris Johnsen,
33

ta linia powłoki może również pokazywać rozmiar bufora potoku:

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

(wysyłanie fragmentów 1k do zablokowanej rury, aż bufor się zapełni) ... niektóre wyniki testu:

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

najkrótsza bash-one-liner przy użyciu printf:

M=0; while printf A; do >&2 printf "\r$((++M)) B"; done | sleep 999
Asain Kujovic
źródło
11
Bardzo dobrze! (dd if=/dev/zero bs=1 | sleep 999) &następnie poczekaj sekundę i killall -SIGUSR1 dddaje 65536 bytes (66 kB) copied, 5.4987 s, 11.9 kB/s- tak samo jak twoje rozwiązanie, ale w rozdzielczości 1 bajta;)
frostschutz
2
Dla przypomnienia w systemie Solaris 10/11 SPARC / x86 ddkomenda blokuje przy 16 KiB. W Fedorze 23/25 x86-64 blokuje przy 64 KiB.
maxschlepzig
1
@frostschutz: To miłe uproszczenie. Pragmatycznie można po prostu pobiec dd if=/dev/zero bs=1 | sleep 999na pierwszym planie, odczekać sekundę, a następnie nacisnąć ^C. Jeśli chciałeś mieć Linuksa na Linuksie i BSD / macOS (bardziej solidny niż przy użyciu killall):dd if=/dev/zero bs=1 | sleep 999 & sleep 1 && pkill -INT -P $$ -x dd
mklement0
7

Oto kilka innych możliwości sprawdzenia rzeczywistej pojemności bufora potoku przy użyciu tylko poleceń powłoki:

# 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
źródło
W systemie Solaris 10 getconf PIPE_BUF /drukuje, 5120który pasuje do ulimit -a | grep pipewyniku, ale nie pasuje do 16 KiB, po którym dd .. | sleep ...blokuje.
maxschlepzig
Na Fedorze 25 twoja pierwsza yesmetoda drukuje 73728zamiast 64 KiB ustalonych za pomocądd if=/dev/zero bs=4096 status=none | pv -bn | sleep 1
maxschlepzig
6

To szybki i brudny hack na 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
źródło
0
$ ulimit -a | grep pipe
pipe size            (512 bytes, -p) 8

Więc na moim Linux-ie mam domyślnie 8 * 512 = 4096 bajtów potoków.

Solaris i wiele innych systemów ma podobną funkcję ulimit.

Sam Watkins
źródło
2
To drukuje (512 bytes, -p) 8na Fedorze 23/25 i 512 bytes, -p) 10Solarisie 10 - i te wartości nie pasują do rozmiarów buforów eksperymentalnie wyprowadzonych z blokowaniem dd.
maxschlepzig
0

Jeśli potrzebujesz wartości w Pythonie> = 3.3, oto prosta metoda (zakładając, że możesz uruchomić call out 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()))
młot
źródło