Làm thế nào lớn là bộ đệm ống?


146

Như một nhận xét trong tôi đang bối rối về lý do tại sao "| true" trong tệp tạo tệp có tác dụng tương tự như "|| true" người dùng cjm đã viết:

Một lý do khác để tránh | đúng là nếu lệnh tạo ra đầu ra đủ để lấp đầy bộ đệm ống, nó sẽ chặn chờ đúng để đọc nó.

Chúng ta có một số cách để tìm ra kích thước của bộ đệm ống là gì?

Câu trả lời:


142

Công suất của bộ đệm ống khác nhau giữa các hệ thống (và thậm chí có thể thay đổi trên cùng một hệ thống). Tôi không chắc chắn có một cách nhanh chóng, dễ dàng và đa nền tảng để chỉ tìm kiếm công suất của một đường ống.

Ví dụ, Mac OS X sử dụng dung lượng 16384 byte theo mặc định, nhưng có thể chuyển sang dung lượng 65336 byte nếu ghi lớn được thực hiện cho đường ống hoặc sẽ chuyển sang dung lượng của một trang hệ thống nếu đã có quá nhiều bộ nhớ kernel được sử dụng bởi các bộ đệm ống (xem xnu/bsd/sys/pipe.h, và xnu/bsd/kern/sys_pipe.c; vì đây là từ FreeBSD, nên hành vi tương tự cũng có thể xảy ra ở đó).

Một trang man của Linux pipe (7) nói rằng dung lượng ống là 65536 byte kể từ Linux 2.6.11 và một trang hệ thống duy nhất trước đó (ví dụ 4096 byte trên các hệ thống x86 (32 bit)). Mã ( include/linux/pipe_fs_i.hfs/pipe.c) dường như sử dụng 16 trang hệ thống (tức là 64 KiB nếu một trang hệ thống là 4 KiB), nhưng bộ đệm cho mỗi ống có thể được điều chỉnh thông qua một fcntl trên đường ống (tối đa công suất tối đa mặc định là 1048576 byte, nhưng có thể được thay đổi thông qua /proc/sys/fs/pipe-max-size)).


Đây là một kết hợp bash / perl nhỏ mà tôi đã sử dụng để kiểm tra dung lượng đường ống trên hệ thống của mình:

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

Đây là những gì tôi tìm thấy khi chạy nó với các kích cỡ ghi khác nhau trên hệ thống Mac OS X 10.6.7 (lưu ý thay đổi đối với ghi lớn hơn 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

Kịch bản tương tự trên 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

Lưu ý: PIPE_BUFGiá trị được xác định trong tệp tiêu đề C (và giá trị pathconf cho _PC_PIPE_BUF), không chỉ định dung lượng của đường ống, nhưng số byte tối đa có thể được ghi nguyên tử (xem ghi POSIX (2) ).

Trích dẫn từ 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
Câu trả lời chính xác. Đặc biệt đối với liên kết đến POSIX write (2), cho biết: Kích thước hiệu quả của một đường ống hoặc FIFO (số tiền tối đa có thể được ghi trong một thao tác mà không bị chặn) có thể thay đổi linh hoạt, tùy thuộc vào việc triển khai, do đó không thể thực hiện được để chỉ định một giá trị cố định cho nó.
Mikel

5
Cảm ơn đã đề cập fcntl()trên Linux; Tôi đã dành một khoảng thời gian để tìm kiếm các chương trình đệm không gian người dùng vì tôi nghĩ các ống tích hợp không có bộ đệm đủ lớn. Bây giờ tôi thấy rằng họ làm, nếu tôi có CAP_SYS_RESOURCE hoặc root sẵn sàng mở rộng kích thước ống tối đa. Vì những gì tôi muốn sẽ chỉ được chạy trên một máy tính Linux cụ thể, nên đây không phải là vấn đề.
Daniel H

1
Bạn có thể vui lòng giải thích ý tưởng cơ bản của kịch bản của bạn? Tôi đang nhìn chằm chằm vào nó và tôi không thể tìm ra cách nó hoạt động? Đặc biệt mục đích sử dụng dấu ngoặc nhọn ở đây VAR = $ ({}) là gì? Cảm ơn bạn.
Wakan Tanka

@WakanTanka: Có một chút để mô tả trong một nhận xét, nhưng cấu trúc cụ thể đó là một phép gán tham số ( var=…) của đầu ra của một thay thế lệnh ( $(…)) bao gồm các lệnh được nhóm ( {…}(…)). Nó cũng sử dụng một số chuyển hướng ( ít phổ biến hơn) (nghĩa là 0<&-3>&1).
Chris Johnsen

2
@WakanTanka: Chương trình Perl ghi vào thiết bị xuất chuẩn của nó (một ống được tạo ra bằng vỏ sò, một ống đang được thử nghiệm) trong các khối có kích thước nhất định và báo cáo cho thiết bị của nó tổng cộng bao nhiêu nó đã viết (cho đến khi nó bị lỗi Thông thường vì bộ đệm của ống đầy hoặc có thể do đầu đọc của ống đã bị đóng sau một thời gian ngắn ( exec 0<&-)). Báo cáo cuối cùng được thu thập ( tail -1) và được in cùng với kích thước ghi.
Chris Johnsen

33

dòng vỏ này cũng có thể hiển thị kích thước bộ đệm ống:

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

(gửi các đoạn 1k đến đường ống bị chặn cho đến khi bộ đệm đầy) ... một số kết quả thử nghiệm:

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

bash-one-liner ngắn nhất sử dụng printf:

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

11
Rất đẹp! (dd if=/dev/zero bs=1 | sleep 999) &sau đó đợi một giây và killall -SIGUSR1 ddđưa ra 65536 bytes (66 kB) copied, 5.4987 s, 11.9 kB/s- giống như giải pháp của bạn, nhưng ở độ phân giải 1 byte;)
frostschutz

2
Đối với bản ghi, trên Solaris 10/11 SPARC / x86, ddkhối lệnh ở 16 KiB. Trên Fedora 23/25 x86-64, nó chặn ở 64 KiB.
maxschlepzig

1
@frostschutz: Đó là một sự đơn giản hóa tốt đẹp. Thực tế, bạn chỉ có thể chạy dd if=/dev/zero bs=1 | sleep 999ở phía trước, đợi một giây, sau đó nhấn ^C. Nếu bạn muốn có một lớp lót trên Linux và BSD / macOS (mạnh hơn so với sử dụng killall):dd if=/dev/zero bs=1 | sleep 999 & sleep 1 && pkill -INT -P $$ -x dd
mkuity0

7

Dưới đây là một số lựa chọn thay thế khác để khám phá dung lượng bộ đệm ống thực tế chỉ sử dụng các lệnh 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

Trên Solaris 10, getconf PIPE_BUF /các bản in 5120phù hợp với ulimit -a | grep pipeđầu ra nhưng không khớp với 16 KiB sau dd .. | sleep ...các khối.
maxschlepzig

Trên Fedora 25, yesphương thức đầu tiên của bạn in 73728thay vì 64 KiB được xác định vớidd if=/dev/zero bs=4096 status=none | pv -bn | sleep 1
maxschlepzig

6

Đây là một bản hack nhanh và bẩn trên 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

Vì vậy, trên hộp Linux của tôi, tôi có các ống 8 * 512 = 4096 byte theo mặc định.

Solaris và nhiều hệ thống khác có chức năng ulimit tương tự.


2
Bản in này (512 bytes, -p) 8trên Fedora 23/25 và 512 bytes, -p) 10trên Solaris 10 - và các giá trị đó không khớp với kích thước bộ đệm có nguồn gốc thực nghiệm với chặn dd.
maxschlepzig

0

Nếu bạn cần giá trị trong Python> = 3.3, đây là một phương thức đơn giản (giả sử bạn có thể chạy lệnh gọi ra 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()))
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.