Seberapa besar penyangga pipa?

Jawaban:

142

Kapasitas penyangga pipa bervariasi antar sistem (dan bahkan dapat bervariasi pada sistem yang sama). Saya tidak yakin ada cara cepat, mudah, dan lintas platform untuk hanya melihat kapasitas pipa.

Mac OS X, misalnya, menggunakan kapasitas 16384 byte secara default, tetapi dapat beralih ke kapasitas 65336 byte jika penulisan besar dilakukan ke pipa, atau akan beralih ke kapasitas halaman sistem tunggal jika terlalu banyak memori kernel sudah sedang digunakan oleh penyangga pipa (lihat xnu/bsd/sys/pipe.h, dan xnu/bsd/kern/sys_pipe.c; karena ini dari FreeBSD, perilaku yang sama juga bisa terjadi di sana).

Satu halaman manual pipa Linux (7) mengatakan bahwa kapasitas pipa adalah 65536 byte sejak Linux 2.6.11 dan satu halaman sistem sebelumnya (mis. 4096 byte pada (32-bit) sistem x86). Kode ( include/linux/pipe_fs_i.h, dan fs/pipe.c) tampaknya menggunakan 16 halaman sistem (yaitu 64 KiB jika halaman sistem adalah 4 KiB), tetapi buffer untuk setiap pipa dapat disesuaikan melalui fcntl pada pipa (hingga kapasitas maksimum yang default ke 1048576 byte, tetapi dapat diubah melalui /proc/sys/fs/pipe-max-size)).


Berikut adalah sedikit kombinasi bash / perl yang saya gunakan untuk menguji kapasitas pipa pada sistem saya:

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

Inilah yang saya temukan menjalankannya dengan berbagai ukuran tulis pada sistem Mac OS X 10.6.7 (perhatikan perubahan untuk penulisan yang lebih besar dari 16KiB):

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

Skrip yang sama di 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

Catatan: PIPE_BUFNilai yang ditentukan dalam file header C (dan nilai pathconf untuk _PC_PIPE_BUF), tidak menentukan kapasitas pipa, tetapi jumlah maksimum byte yang dapat ditulis secara atom (lihat POSIX write (2) ).

Kutipan dari 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
sumber
14
Jawaban yang bagus Khusus untuk tautan ke POSIX write (2), yang mengatakan: Ukuran efektif pipa atau FIFO (jumlah maksimum yang dapat ditulis dalam satu operasi tanpa pemblokiran) dapat bervariasi secara dinamis, tergantung pada implementasinya, sehingga tidak mungkin untuk menentukan nilai tetap untuk itu.
Mikel
5
Terima kasih telah menyebutkan fcntl()di Linux; Saya telah menghabiskan waktu mencari program buffering userspace karena saya pikir pipa bawaan tidak memiliki buffer yang cukup besar. Sekarang saya melihat mereka melakukannya, jika saya memiliki CAP_SYS_RESOURCE atau root bersedia untuk memperluas ukuran pipa maksimum. Seperti yang saya inginkan hanya akan berjalan di komputer Linux tertentu (milik saya), ini seharusnya tidak menjadi masalah.
Daniel H
1
Bisakah Anda jelaskan ide dasar skrip Anda? Saya menatapnya dan saya tidak tahu cara kerjanya? Terutama apa tujuan menggunakan kurung keriting di sini VAR = $ ({})? Terima kasih.
Wakan Tanka
@WakanTanka: Agak banyak untuk dijelaskan dalam komentar, tapi itu membangun tertentu adalah penugasan parameter ( var=…) dari output substitusi perintah ( $(…)) yang mencakup perintah dikelompokkan ( {…}, dan (…)). Itu juga menggunakan beberapa pengalihan ( kurang umum) (yaitu 0<&-dan 3>&1).
Chris Johnsen
2
@WakanTanka: Program Perl menulis ke stdout-nya (pipa yang dibuat oleh shell — yang sedang diuji) dalam blok-blok dengan ukuran tertentu dan melaporkan kepada stderr sejumlah total dari berapa banyak yang telah ditulisnya (sampai ia mendapatkan kesalahan) - biasanya karena penyangga pipa penuh atau mungkin karena ujung pembacaan pipa telah ditutup setelah waktu yang singkat ( exec 0<&-)). Laporan akhir dikumpulkan ( tail -1) dan dicetak bersama dengan ukuran penulisan.
Chris Johnsen
33

shell-line ini juga dapat menunjukkan ukuran buffer pipa:

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

(mengirim potongan 1k ke pipa yang diblokir sampai buffer penuh) ... beberapa hasil uji:

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

bash-one-liner terpendek menggunakan printf:

M=0; while printf A; do >&2 printf "\r$((++M)) B"; done | sleep 999
Asain Kujovic
sumber
11
Sangat bagus! (dd if=/dev/zero bs=1 | sleep 999) &lalu tunggu sebentar dan killall -SIGUSR1 ddberikan 65536 bytes (66 kB) copied, 5.4987 s, 11.9 kB/s- sama seperti solusi Anda, tetapi pada resolusi 1 byte;)
frostschutz
2
Sebagai catatan, pada Solaris 10/11 SPARC / x86 ddblok perintah di 16 KiB. Pada Fedora 23/25 x86-64, ia memblokir di 64 KiB.
maxschlepzig
1
@ frostschutz: Itu penyederhanaan yang bagus. Secara pragmatis, Anda bisa berlari dd if=/dev/zero bs=1 | sleep 999di latar depan, tunggu sebentar, lalu tekan ^C. Jika Anda menginginkan one-liner di Linux dan BSD / macOS (lebih kuat daripada menggunakan killall):dd if=/dev/zero bs=1 | sleep 999 & sleep 1 && pkill -INT -P $$ -x dd
mklement0
7

Berikut adalah beberapa alternatif lebih lanjut untuk mengeksplorasi kapasitas buffer pipa yang sebenarnya menggunakan perintah shell saja:

# 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
sumber
Pada Solaris 10, getconf PIPE_BUF /cetakan 5120yang cocok dengan ulimit -a | grep pipeoutput tetapi tidak cocok dengan 16 KiB setelah dd .. | sleep ...blok mana .
maxschlepzig
Pada Fedora 25, yesmetode pertama Anda mencetak 73728bukannya 64 KiB yang ditentukan dengandd if=/dev/zero bs=4096 status=none | pv -bn | sleep 1
maxschlepzig
6

Ini adalah hack cepat dan kotor di 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
sumber
0
$ ulimit -a | grep pipe
pipe size            (512 bytes, -p) 8

Jadi pada kotak Linux saya, saya memiliki 8 * 512 = 4096 byte pipa secara default.

Solaris dan banyak sistem lainnya memiliki fungsi ulimit yang serupa.

Sam Watkins
sumber
2
Ini mencetak (512 bytes, -p) 8pada Fedora 23/25 dan 512 bytes, -p) 10pada Solaris 10 - dan nilai-nilai itu tidak cocok dengan ukuran buffer yang diperoleh secara eksperimental dengan pemblokiran dd.
maxschlepzig
0

Jika Anda membutuhkan nilai dalam Python> = 3.3, berikut adalah metode sederhana (dengan asumsi Anda dapat menjalankan panggilan keluar ke 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()))
tidak tahu malu
sumber