Qual é o tamanho do buffer do tubo?

Respostas:

142

A capacidade de um buffer de tubo varia entre os sistemas (e pode até variar no mesmo sistema). Não tenho certeza de que exista uma maneira rápida, fácil e multiplataforma de apenas pesquisar a capacidade de um tubo.

O Mac OS X, por exemplo, usa uma capacidade de 16384 bytes por padrão, mas pode alternar para capacidades de 65336 bytes se forem feitas grandes gravações no canal ou alternará para a capacidade de uma única página do sistema se já houver muita memória do kernel sendo usado por buffers de pipe (veja xnu/bsd/sys/pipe.h, e xnu/bsd/kern/sys_pipe.c; como são do FreeBSD, o mesmo comportamento também pode acontecer lá).

Uma página do manual pipe (7) do Linux diz que a capacidade do pipe é de 65536 bytes desde o Linux 2.6.11 e uma única página do sistema anterior (por exemplo, 4096 bytes em sistemas x86 (32 bits)). O código ( include/linux/pipe_fs_i.h, e fs/pipe.c) parece usar 16 páginas do sistema (ou seja, 64 KiB se uma página do sistema for 4 KiB), mas o buffer de cada canal pode ser ajustado através de um fcntl no canal (até uma capacidade máxima padrão 1048576 bytes, mas pode ser alterado via /proc/sys/fs/pipe-max-size)).


Aqui está uma pequena combinação de bash / perl que eu usei para testar a capacidade do tubo no meu sistema:

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

Aqui está o que eu achei executando-o com vários tamanhos de gravação em um sistema Mac OS X 10.6.7 (observe a alteração para gravações maiores que 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

O mesmo script no 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

Nota: O PIPE_BUFvalor definido nos arquivos de cabeçalho C (e o valor pathconf para _PC_PIPE_BUF) não especifica a capacidade dos pipes, mas o número máximo de bytes que podem ser gravados atomicamente (consulte POSIX write (2) ).

Citação de 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
fonte
14
Ótima resposta. Especialmente para o link para o POSIX write (2), que diz: O tamanho efetivo de um canal ou FIFO (a quantidade máxima que pode ser gravada em uma operação sem bloqueio) pode variar dinamicamente, dependendo da implementação, portanto, não é possível para especificar um valor fixo para ele.
25411 Mikel
5
Obrigado por mencionar fcntl()no Linux; Eu havia passado algum tempo procurando programas de buffer no espaço do usuário porque achava que os pipes internos não tinham um buffer grande o suficiente. Agora vejo que sim, se eu tiver CAP_SYS_RESOURCE ou o root estiver disposto a expandir o tamanho máximo do tubo. Como o que eu quero será executado apenas em um computador Linux específico (o meu), isso não deve ser um problema.
Daniel H
11
Você pode, por favor, explicar a idéia básica do seu script? Estou olhando para ele e não consigo descobrir como funciona? Especialmente qual é o propósito de usar colchetes aqui VAR = $ ({})? Obrigado.
Wakan Tanka
@WakanTanka: É um pouco demais descrever em um comentário, mas essa construção em particular é uma atribuição de parâmetro ( var=…) da saída de uma substituição de comando ( $(…)) que inclui comandos agrupados ( {…}, e (…)). Ele também usa vários redirecionamentos ( menos comuns) (ie 0<&-e 3>&1).
Chris Johnsen
2
@WakanTanka: O programa Perl grava em seu stdout (um pipe criado por shell - aquele que está sendo testado) em blocos de um determinado tamanho e informa ao stderr um total contínuo de quanto ele gravou (até receber um erro - geralmente porque o buffer do tubo está cheio ou possivelmente porque a extremidade de leitura do tubo foi fechada após um curto período de tempo ( exec 0<&-)). O relatório final é coletado ( tail -1) e impresso junto com o tamanho da gravação.
Chris Johnsen
33

essa linha de shell também pode mostrar o tamanho do buffer do tubo:

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

(enviando pedaços de 1k ao tubo bloqueado até o buffer ficar cheio) ... algumas saídas de teste:

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

menor bash-one-liner usando printf:

M=0; while printf A; do >&2 printf "\r$((++M)) B"; done | sleep 999
Asain Kujovic
fonte
11
Muito agradável! (dd if=/dev/zero bs=1 | sleep 999) &em seguida, espere um segundo e killall -SIGUSR1 dd65536 bytes (66 kB) copied, 5.4987 s, 11.9 kB/s- o mesmo que a sua solução, mas a resolução de 1 byte;)
frostschutz
2
Para o registro, no Solaris 10/11 SPARC / x86 o ddcomando é bloqueado em 16 KiB. No Fedora 23/25 x86-64, ele bloqueia em 64 KiB.
maxschlepzig
11
@frostschutz: Essa é uma boa simplificação. Pragmaticamente, você pode simplesmente correr dd if=/dev/zero bs=1 | sleep 999em primeiro plano, esperar um segundo e pressionar ^C. Se você queria um one-liner em Linux e BSD / MacOS (mais robusto do que usar killall):dd if=/dev/zero bs=1 | sleep 999 & sleep 1 && pkill -INT -P $$ -x dd
mklement0
7

Aqui estão algumas alternativas adicionais para explorar a capacidade real do buffer de tubo usando apenas comandos shell:

# 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
fonte
No Solaris 10, getconf PIPE_BUF /imprime 5120que corresponde à ulimit -a | grep pipesaída, mas não aos 16 KiB após os quais dd .. | sleep ...bloqueia.
maxschlepzig
No Fedora 25, o seu primeiro yesmétodo imprime 73728em vez dos 64 KiB determinada comdd if=/dev/zero bs=4096 status=none | pv -bn | sleep 1
maxschlepzig
6

Este é um hack rápido e sujo no 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
fonte
0
$ ulimit -a | grep pipe
pipe size            (512 bytes, -p) 8

Então, na minha caixa Linux, tenho 8 * 512 = 4096 bytes por padrão.

Solaris e muitos outros sistemas têm uma função ulimit semelhante.

Sam Watkins
fonte
2
Isso é impresso (512 bytes, -p) 8no Fedora 23/25 e 512 bytes, -p) 10no Solaris 10 - e esses valores não correspondem aos tamanhos de buffer derivados experimentalmente com um bloqueio dd.
maxschlepzig
0

Se você precisar do valor em Python> = 3.3, aqui está um método simples (supondo que você possa executar a chamada 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()))
desenfreado
fonte