パイプバッファーの大きさは?


146

のコメントとして、makefileの「| true」がユーザーcjmが書いた「|| true」と同じ効果がある理由について混乱しています。

避けるべきもう一つの理由| trueは、コマンドがパイプバッファーをいっぱいにするのに十分な出力を生成した場合、trueを待ってブロックすることです。

パイプバッファーのサイズを調べる方法はありますか?

回答:


142

パイプバッファーの容量はシステムによって異なります(同じシステムでも異なる場合があります)。パイプの容量を単に検索するための、迅速で簡単なクロスプラットフォームの方法があるかどうかはわかりません。

たとえば、Mac OS Xはデフォルトで16384バイトの容量を使用しますが、パイプに大量の書き込みが行われる場合は65336バイトの容量に切り替えることができ、カーネルメモリが多すぎる場合は単一のシステムページの容量に切り替えますパイプバッファによって使用されている(参照xnu/bsd/sys/pipe.h、そしてxnu/bsd/kern/sys_pipe.c、これらは、FreeBSDからのものであるので、同じ動作があまりにも、そこに起こり得ます)。

1つのLinux pipe(7) manページには、Linux 2.6.11以降のパイプ容量は65536バイトで、その前の単一のシステムページ(たとえば(32ビット)x86システムでは4096バイト)であると書かれています。コード(include/linux/pipe_fs_i.h、およびfs/pipe.c)は16システムページ(つまり、システムページが4 KiBの場合は64 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システムでさまざまな書き込みサイズで実行したことがわかりました(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

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_BUFCヘッダーファイルで定義されている値(およびの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の有効サイズ(ブロックせずに1回の操作で書き込むことができる最大量)は、実装によって動的に変化する可能性があるため、不可能です固定値を指定します。
ミケル

5
fcntl()Linux について言及してくれてありがとう。組み込みのパイプには十分な大きさのバッファーがないと思ったので、ユーザー空間のバッファープログラムを探していました。CAP_SYS_RESOURCEを持っているか、rootが最大パイプサイズを拡大することを望んでいる場合、それらが実行されることがわかります。私が欲しいものは特定のLinuxコンピューター(私の)でのみ実行されるので、これは問題になりません。
ダニエルH

1
スクリプトの基本的な考え方を説明してもらえますか?私はそれを見つめていますが、どのように機能するのかわかりませんか?特にここで中括弧を使用する目的は何ですかVAR = $({})?ありがとうございました。
和観短歌

@WakanTanka:コメントで説明するのは少し大変ですが、その特定の構成は、グループ化されたコマンド(、および)を含むコマンド置換()の出力のパラメーター割り当てvar=…)です。また、いくつかの(あまり一般的ではない)リダイレクト(および)を使用します。$(…){…}(…)0<&-3>&1
クリスジョンセン

2
@WakanTanka:Perlプログラムは、指定されたサイズのブロックで標準出力(シェルで作成されたパイプ-テスト対象のパイプ)に書き込み、エラーが発生するまで、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.

printfを使用した最短bash-one-liner:

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は、フォアグラウンドで実行し、1秒間待ってからを押すことができます^C。LinuxおよびBSD / macOSでワンライナーが必要な場合(を使用するよりも堅牢killall):dd if=/dev/zero bs=1 | sleep 999 & sleep 1 && pkill -INT -P $$ -x dd
mklement0

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個のKiBの後に一致していないdd .. | sleep ...ブロックを。
maxschlepzig

Fedora 25では、yes73728dd 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()))
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.