Насколько велика труба буфера?

146

В качестве комментария я запутался, почему «| true» в make- файле имеет тот же эффект, что и «|| true» пользователь cjm пишет:

Еще одна причина, чтобы избежать | Значение true состоит в том, что если команда выдаст достаточно выходных данных для заполнения буфера канала, она заблокирует ожидание истины для ее чтения.

У нас есть какой-нибудь способ узнать, каков размер буфера канала?

Кит Сунде
источник

Ответы:

142

Емкость конвейерного буфера варьируется в зависимости от системы (и может даже различаться в одной и той же системе). Я не уверен, что существует быстрый, простой и кроссплатформенный способ просто проверить емкость трубы.

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

Одна справочная страница Linux pipe (7) сообщает, что емкость канала составляет 65536 байт начиная с Linux 2.6.11 и одну системную страницу до этого (например, 4096 байт в (32-разрядных) системах x86). Код ( include/linux/pipe_fs_i.h, и fs/pipe.c), по-видимому, использует 16 системных страниц (т. Е. 64 КиБ, если системная страница равна 4 КиБ), но буфер для каждого канала можно настроить с помощью 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 (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 (2), которая гласит: Эффективный размер канала или FIFO (максимальный объем, который может быть записан за одну операцию без блокировки) может изменяться динамически, в зависимости от реализации, поэтому это невозможно указать фиксированное значение для него.
Микель
5
Спасибо за упоминание fcntl()о Linux; Я потратил некоторое время на поиск программ буферизации в пользовательском пространстве, потому что думал, что встроенные каналы не имеют достаточно большого буфера. Теперь я вижу, что они делают, если у меня есть CAP_SYS_RESOURCE или root хочет расширить максимальный размер канала. Поскольку то, что я хочу, будет работать только на определенном компьютере с Linux (мой), это не должно быть проблемой.
Даниэль Х
1
Можете ли вы объяснить основную идею вашего сценария? Я смотрю на это, и я не могу понять, как это работает? Особенно, для чего здесь используются фигурные скобки VAR = $ ({})? Спасибо.
Вакан Танка
@WakanTanka: это немного много, чтобы описать в комментарии, но эта конкретная конструкция является параметром assignment ( var=…) для вывода команды substitution ( $(…)), которая включает в себя сгруппированные команды ( {…}, и (…)). Он также использует несколько (менее распространенных) перенаправлений (то есть 0<&-и 3>&1).
Крис Джонсен
2
@WakanTanka: Perl-программа записывает в свой стандартный вывод (созданный оболочкой канал - тот, который тестируется) в блоках заданного размера и сообщает своему стандартному счетчику промежуточный итог того, сколько она написала (пока не получит ошибку - обычно потому, что буфер канала заполнен или, возможно, потому что конец чтения канала был закрыт через короткое время ( 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 КиБ. На Fedora 23/25 x86-64 он блокируется на 64 КиБ.
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 КиБ, определенных с помощью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) 10на Solaris 10 - и эти значения не соответствуют размерам буфера, экспериментально полученным с блокировкой dd.
maxschlepzig
0

Если вам нужно значение в Python> = 3.3, вот простой метод (при условии, что вы можете запустить call to 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
источник