Наскільки великий буфер для труб?


146

Як коментар, я збентежений, чому "| true" у makefile має такий же ефект, як "|| true", користувач cjm написав:

Ще одна причина уникати | правда полягає в тому, що якби команда створила достатній вихід для заповнення буфера труби, вона заблокувала б очікування істинного для її читання.

Чи є у нас якийсь спосіб з’ясувати, який розмір буфера труб?

Відповіді:


142

Місткість буферної труби змінюється в різних системах (і може змінюватися навіть в одній системі). Я не впевнений, що існує швидкий, простий і перехресний платформний спосіб просто знайти ємність труби.

Наприклад, Mac OS X використовує ємність 16384 байт за замовчуванням, але може переключитися на 65336 байт ємності, якщо велике записування буде зроблено в трубу, або перейде на ємність однієї системної сторінки, якщо вже занадто багато пам'яті ядра використовуються буферними трубами (див. xnu/bsd/sys/pipe.h, і xnu/bsd/kern/sys_pipe.cоскільки вони є з FreeBSD, така ж поведінка може відбуватися і там).

На одній підручній сторінці Linux (7) написано, що ємність трубопроводу становить 65536 байт з моменту Linux 2.6.11, а до цього - одна системна сторінка (наприклад, 4096 байт у 32-бітних системах x86). Здається, що в коді ( include/linux/pipe_fs_i.hі fs/pipe.c) використовується 16 системних сторінок (тобто 64 KiB, якщо на системній сторінці 4 KiB), але буфер для кожної труби може бути відрегульований за допомогою fcntl на трубі (до максимальної потужності, яка за замовчуванням становить 1048576 байтів, але їх можна змінити через /proc/sys/fs/pipe-max-size)).


Ось невелика комбінація bash / perl, яку я використовував для перевірки ємності труби у своїй системі:

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

Ось що я виявив, що він працює з різними розмірами запису в системі Mac OS X 10.6.7 (зверніть увагу на зміну для записів, більших за 16 Кбіт):

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

Той самий сценарій на 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

Примітка. PIPE_BUFЗначення, визначене у файлах заголовка C (та значення pathconf для _PC_PIPE_BUF), не визначає ємність труб, а максимальну кількість байтів, які можна записати атомно (див. POSIX write (2) ).

Цитата від 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
Чудова відповідь. Спеціально для посилання на POSIX write (2), де сказано: Ефективний розмір труби або FIFO (максимальна кількість, яку можна записати за одну операцію без блокування) може динамічно змінюватися в залежності від реалізації, тому це неможливо визначити для нього фіксоване значення.
Мікель

5
Дякуємо, що згадали fcntl()про Linux; Я витратив деякий час на пошуки програм буферизації простору користувачів, тому що думав, що вбудовані труби не мають достатньо великого буфера. Тепер я бачу, що вони є, якщо у мене є CAP_SYS_RESOURCE або root готовий розширити максимальний розмір труби. Оскільки те, що я хочу, буде працювати лише на певному комп'ютері Linux (моєму), це не повинно бути проблемою.
Даніель Н

1
Чи можете ви пояснити основну ідею вашого сценарію? Я дивлюся на це і не можу зрозуміти, як це працює? Особливо, яка мета використання тут фігурних дужок VAR = $ ({})? Дякую.
Вакан Танка

@WakanTanka: Це трохи описати в коментарі, але ця конкретна конструкція - це присвоєння параметра ( var=…) виводу підстановки команд ( $(…)), що включає згруповані команди ( {…}і та (…)). Він також використовує кілька (менш поширених) переадресацій (тобто 0<&-і 3>&1).
Кріс Джонсен

2
@WakanTanka: Програма Perl записує у свій stdout (створена оболонкою труба - та, яка тестується) в блоки заданого розміру і повідомляє своєму stderr загальний обсяг того, скільки написано (поки не з’явиться помилка —Зазвичай тому, що буфер труби заповнений або, можливо, через те, що кінець зчитування труби закритий через короткий час ( exec 0<&-)). Остаточний звіт збирається ( tail -1) та друкується разом із розміром запису.
Кріс Джонсен

33

ця лінія оболонки може також показувати розмір буфера труби:

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

(відправлення 1k шматки до заблокованої труби до заповнення буфера) ... деякі тестові виходи:

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

найкоротший bash-one-liner з використанням printf:

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

11
Дуже хороша! (dd if=/dev/zero bs=1 | sleep 999) &потім зачекайте секунду і killall -SIGUSR1 ddдає 65536 bytes (66 kB) copied, 5.4987 s, 11.9 kB/s- те саме, що і ваше рішення, але з роздільною здатністю 1 байт;)
frostschutz

2
Для запису, на Solaris 10/11 SPARC / x86 ddкомандні блоки на 16 KiB. У Fedora 23/25 x86-64 він блокується на 64 KiB.
maxschlepzig

1
@frostschutz: Це приємне спрощення. Прагматично ви можете просто бігти dd if=/dev/zero bs=1 | sleep 999на передній план, зачекати секунду, а потім натиснути ^C. Якщо ви хотіли killalldd if=/dev/zero bs=1 | sleep 999 & sleep 1 && pkill -INT -P $$ -x dd
однолінійку

7

Ось декілька подальших альтернатив для дослідження фактичної ємності буфера труб лише за допомогою команд оболонки:

# 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

На Solaris 10 getconf PIPE_BUF /друкується, 5120що відповідає ulimit -a | grep pipeрезультату, але не відповідає 16 КБ, після чого dd .. | sleep ...блокується.
maxschlepzig

У Fedora 25 ваш перший yesметод друкує 73728замість 64 KiB, визначений зdd if=/dev/zero bs=4096 status=none | pv -bn | sleep 1
maxschlepzig

6

Це швидкий і брудний злом на 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

Тож у моєму вікні Linux я за замовчуванням маю 8 * 512 = 4096 байтових труб.

Solaris та багато інших систем мають подібну функцію ulimit.


2
Це друкується (512 bytes, -p) 8на Fedora 23/25 та 512 bytes, -p) 10Solaris 10 - і ці значення не відповідають розмірам буфера, експериментально отриманим із блокуванням dd.
maxschlepzig

0

Якщо вам потрібно значення в Python> = 3.3, ось простий метод (якщо припустити, що ви можете запустити виклик 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()))
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.