บัฟเฟอร์ของท่อใหญ่แค่ไหน


146

ในขณะที่ความคิดเห็นในฉันสับสนว่าทำไม "| จริง" ใน makefile มีผลเช่นเดียวกับผู้ใช้"|| จริง"ผู้ใช้cjmเขียน:

อีกเหตุผลที่ควรหลีกเลี่ยง | true คือถ้าคำสั่งสร้างเอาต์พุตเพียงพอเพื่อเติมบัฟเฟอร์ของไพพ์มันจะบล็อกการรอให้ true อ่าน

เรามีวิธีในการค้นหาขนาดของบัฟเฟอร์ของท่อหรือไม่?

คำตอบ:


142

ความจุของไพพ์บัฟเฟอร์แตกต่างกันไปตามระบบ (และอาจแตกต่างกันไปในระบบเดียวกัน) ฉันไม่แน่ใจว่ามีวิธีที่รวดเร็วง่ายและข้ามแพลตฟอร์มเพื่อค้นหาความจุของไพพ์

ตัวอย่างเช่น Mac OS X ใช้ความจุ 16384 ไบต์ตามค่าเริ่มต้น แต่สามารถเปลี่ยนเป็นความจุ 65336 ไบต์หากการเขียนขนาดใหญ่ถูกทำกับไพพ์หรือจะเปลี่ยนเป็นความจุของหน้าระบบเดียวหากหน่วยความจำเคอร์เนลมากเกินไปอยู่แล้ว ถูกใช้โดยบัฟเฟอร์ไปป์ (ดูxnu/bsd/sys/pipe.hและxnu/bsd/kern/sys_pipe.cเนื่องจากสิ่งเหล่านี้มาจาก FreeBSD พฤติกรรมแบบเดียวกันอาจเกิดขึ้นที่นั่นเช่นกัน)

หนึ่งหน้า man Linux ท่อ (7)บอกว่าความจุของท่อเป็น 65536 ไบต์ตั้งแต่ Linux 2.6.11 และหน้าระบบเดียวก่อนหน้านั้น (เช่น 4096 ไบต์บนระบบ x86 (32 บิต) x86) โค้ด ( include/linux/pipe_fs_i.hและfs/pipe.c) ดูเหมือนว่าจะใช้หน้าระบบ 16 หน้า (เช่น 64 KiB หากหน้าระบบคือ 4 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_BUFค่าที่กำหนดในไฟล์ส่วนหัว C (และค่า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 (จำนวนสูงสุดที่สามารถเขียนได้ในการดำเนินการหนึ่งครั้งโดยไม่มีการบล็อก) อาจแตกต่างกันไปขึ้นอยู่กับการนำไปใช้ เพื่อระบุค่าคงที่สำหรับมัน
มิเคล

5
ขอบคุณที่พูดถึงfcntl()Linux; ฉันใช้เวลาสักครู่เพื่อค้นหาโปรแกรมบัฟเฟอร์ผู้ใช้เนื่องจากฉันคิดว่าไพพ์ในตัวไม่มีบัฟเฟอร์เพียงพอขนาดใหญ่ ตอนนี้ฉันเห็นว่าพวกเขาทำถ้าฉันมี CAP_SYS_RESOURCE หรือรูทยินดีที่จะขยายขนาดไพพ์สูงสุด สิ่งที่ฉันต้องการจะถูกเรียกใช้บนคอมพิวเตอร์ลีนุกซ์เฉพาะ (ของฉัน) นี่ไม่ควรเป็นปัญหา
Daniel H

1
คุณช่วยอธิบายความคิดพื้นฐานของสคริปต์ของคุณได้ไหม ฉันกำลังจ้องมองและฉันไม่สามารถหาวิธีการใช้งานได้? โดยเฉพาะอย่างยิ่งจุดประสงค์ในการใช้เครื่องหมายปีกกาที่นี่ VAR = $ ({}) คืออะไร ขอขอบคุณ.
Wakan Tanka

@WakanTanka: มันค่อนข้างจะอธิบายได้ในคอมเม้นท์ แต่การสร้างนั้นเป็นการกำหนดพารามิเตอร์ ( var=…) ของเอาท์พุทของการทดแทนคำสั่ง ( $(…)) ที่รวมคำสั่งที่จัดกลุ่มไว้ ( {…}และ(…)) นอกจากนี้ยังใช้การเปลี่ยนเส้นทาง ( พบน้อยกว่า) หลายรายการ(เช่น0<&-และ3>&1)
Chris Johnsen

2
@ WakanTanka: โปรแกรม Perl เขียนไปยัง stdout (ไปป์ที่สร้างโดยเชลล์ - ตัวที่ถูกทดสอบ) ในบล็อกของขนาดที่กำหนดและรายงานไปยัง stderr ของมันรวมจำนวนที่เขียนไว้ (จนกว่าจะได้รับข้อผิดพลาด) - ปกติเพราะบัฟเฟอร์ของไพพ์เต็มหรืออาจเป็นเพราะปลายการอ่านของไพพ์ถูกปิดหลังจากช่วงเวลาสั้น ๆ ( exec 0<&-) รายงานขั้นสุดท้ายจะถูกรวบรวม ( tail -1) และพิมพ์พร้อมกับขนาดการเขียน
Chris Johnsen

33

shell-line นี้สามารถแสดงขนาดบัฟเฟอร์ไพพ์ได้เช่นกัน:

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 KiB ใน Fedora 23/25 x86-64 จะบล็อกที่ 64 KiB
maxschlepzig

1
@frostschutz: นั่นเป็นความเรียบง่ายที่ดี ในทางปฏิบัติคุณก็สามารถทำงานในเบื้องหน้ารอสองแล้วกดdd if=/dev/zero bs=1 | sleep 999 ^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 yesวิธีแรกของคุณจะพิมพ์73728แทน 64 KiB ที่กำหนดด้วย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) 10Solaris 10 - ddและค่าเหล่านั้นไม่ตรงกับขนาดบัฟเฟอร์ที่ได้มาทดลองกับบล็อก
maxschlepzig

0

หากคุณต้องการความคุ้มค่าในหลาม> = 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.