188

As a comment in I'm confused as to why "| true" in a makefile has the same effect as "|| true" user cjm wrote:

Another reason to avoid | true is that if the command produced enough output to fill up the pipe buffer, it would block waiting for true to read it.

Do we have some way of finding out what the size of the pipe buffer is?

8 Answers 8

203

The capacity of a pipe buffer varies across systems (and can even vary on the same system). I am not sure if there is a quick, easy, and cross-platform way to just lookup the capacity of a pipe.

Mac OS X, for example, uses a capacity of 16384 bytes by default, but can switch to 65336 byte capacities if large write are made to the pipe, or will switch to a capacity of a single system page if too much kernel memory is already being used by pipe buffers (see xnu/bsd/sys/pipe.h, and xnu/bsd/kern/sys_pipe.c; since these are from FreeBSD, the same behavior may happen there, too).

One Linux pipe(7) man page says that pipe capacity is 65536 bytes since Linux 2.6.11 and a single system page prior to that (e.g. 4096 bytes on (32-bit) x86 systems). The code (include/linux/pipe_fs_i.h, and fs/pipe.c) seems to use 16 system pages (i.e. 64 KiB if a system page is 4 KiB), but the buffer for each pipe can be adjusted via a fcntl on the pipe (up to a maximum capacity which defaults to 1048576 bytes, but can be changed via /proc/sys/fs/pipe-max-size)).


Here is a little bash/perl combination that I used to test the pipe capacity on my system:

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

Here is what I found running it with various write sizes on a Mac OS X 10.6.7 system (note the change for writes larger than 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

The same script on 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

Note: The PIPE_BUF value defined in the C header files (and the pathconf value for _PC_PIPE_BUF), does not specify the capacity of pipes, but the maximum number of bytes that can be written atomically (see POSIX write(2)).

Quote from 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.  */
9
  • 19
    Great answer. Especially for the link to POSIX write(2), which says: The effective size of a pipe or FIFO (the maximum amount that can be written in one operation without blocking) may vary dynamically, depending on the implementation, so it is not possible to specify a fixed value for it.
    – Mikel
    Commented Apr 25, 2011 at 5:59
  • 5
    Thanks for mentioning fcntl() on Linux; I had spent a while looking for userspace buffering programs because I thought the built-in pipes didn't have a large enough buffer. Now I see that they do, if I have CAP_SYS_RESOURCE or root is willing to expand the maximum pipe size. As what I want will only be run on a specific Linux computer (mine), this shouldn't be a problem.
    – Daniel H
    Commented Aug 4, 2013 at 18:43
  • 1
    Can you please explain the basic idea of your script? I am staring at it and I cannot figure out how it works? Especially what is the purpose using curly brackets here VAR=$({})? Thank you. Commented Mar 12, 2015 at 0:24
  • @WakanTanka: It is a bit much to describe in a comment, but that particular construct is a parameter assignment (var=…) of the output of a command substitution ($(…)) that includes grouped commands ({…}, and (…)). It also uses several (less common) redirections (i.e. 0<&- and 3>&1). Commented Mar 12, 2015 at 0:51
  • 2
    @WakanTanka: The Perl program writes to its stdout (a shell-created pipe—the one that is being tested) in blocks of a given size and reports to its stderr a running total of how much it has written (until it gets an error—usually because the pipe’s buffer is full or possibly because the reading end of the pipe has been closed after a short time (exec 0<&-)). The final report is collected (tail -1) and printed along with the write size. Commented Mar 12, 2015 at 1:27
49

this shell-line can show pipe buffer size too:

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

(sending 1k chunks to blocked pipe until buffer full) ...some test outputs:

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

shortest bash-one-liner using printf:

M=0; while printf A; do >&2 printf "\r$((++M)) B"; done | sleep 999
6
  • 16
    Very nice! (dd if=/dev/zero bs=1 | sleep 999) & then wait a second and killall -SIGUSR1 dd gives 65536 bytes (66 kB) copied, 5.4987 s, 11.9 kB/s - same as your solution, but at 1 byte resolution ;) Commented Apr 17, 2013 at 23:21
  • 3
    For the record, on Solaris 10/11 SPARC/x86 the dd command blocks at 16 KiB. On Fedora 23/25 x86-64, it blocks at 64 KiB. Commented Jan 1, 2017 at 12:08
  • 2
    @frostschutz: That's a nice simplification. Pragmatically, you could just run dd if=/dev/zero bs=1 | sleep 999 in the foreground, wait a second, then press ^C. If you wanted a one-liner on Linux and BSD/macOS (more robust than using killall): dd if=/dev/zero bs=1 | sleep 999 & sleep 1 && pkill -INT -P $$ -x dd
    – mklement0
    Commented Jan 23, 2017 at 18:39
  • 1
    OpenBSD 7.5 gives 16 KB. Commented Jun 5 at 15:12
  • 1
    Fedora 40 (Linux 6.10) gives 64K on x86_64, but 256K on Asahi/Fedora 40 on Apple silicon. Commented Aug 30 at 11:45
8

Here are some further alternatives to explore the actual pipe buffer capacity using shell commands only:

# 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
3
  • On Solaris 10, getconf PIPE_BUF / prints 5120 which matches the ulimit -a | grep pipe output but doesn't match the 16 KiB after which dd .. | sleep ... blocks. Commented Jan 1, 2017 at 12:16
  • On Fedora 25, your first yes method prints 73728 instead of the 64 KiB determined with dd if=/dev/zero bs=4096 status=none | pv -bn | sleep 1 Commented Jan 1, 2017 at 12:30
  • 1
    I too get 73728 from yes | tee >(sleep 1) | wc -c instead of 65536. I think this is because we measure the buffer of >( ) instead of | even though both should use the same mechanism under the hood... strange. With yes | tee >(wc -c >&2) | sleep 1 I get the correct number.
    – Socowi
    Commented Aug 6, 2021 at 20:08
7

This is a quick and dirty hack on 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
7

If you need the value in Python>=3.3, here's a simple method (assuming you can run call out 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()))
1

For finding mklement0's solution more easily, I turned their comment into this answer:

Pragmatically, you could just run dd if=/dev/zero bs=1 | sleep 999 in the foreground, wait a second, then press ^C [= ctrl+C].

If you wanted a one-liner on Linux and BSD/macOS (more robust than using killall):
dd if=/dev/zero bs=1 | sleep 999 & sleep 1 && pkill -INT -P $$ -x dd

With GNU timeout you can simplify this to ...

timeout -s INT 3 dd if=/dev/zero bs=1 | sleep 99

Either way, the output will be something like below.

65537+0 records in
65536+0 records out
65536 bytes (66 kB, 64 KiB) copied, 2.99738 s, 21.9 kB/s

In above example, my buffer was 65536 bytes.
dd couldn't write the 65537th byte, because the buffer was full.

-1

The accepted answer adapted to zsh:

#+begin_src bsh.dash :results verbatim :exports both :wrap example
test-buffer() {
    test $# -ge 1 || { echo "usage: $0 write-size [wait-time]"; return 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"
}
uname
for i in {1..18} ; do
    test-buffer $((2**i)) 1
done
#+end_src

Results:

#+begin_example
Darwin
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
#+end_example
-2

As simple as executing this:

echo "$(($(ulimit -p)*512)) bytes"
1
  • 4
    Unfortunately this is not correct, the value reported by ulimit -p is PIPE_BUF which is the largest size for which writes are guaranteed to be atomic. See stackoverflow.com/a/4625116/813810
    – Diego
    Commented Jan 4, 2022 at 14:22

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .