การใช้ทรัพยากรโดยใช้ไพพ์และนี่สตริง


16

เราสามารถได้รับผลเดียวกันโดยใช้ทั้งสองต่อไปนี้ในbash,

echo 'foo' | cat

และ

cat <<< 'foo'

คำถามของฉันคืออะไรความแตกต่างระหว่างสองสิ่งนี้เท่าที่ใช้ทรัพยากรเกี่ยวข้องและสิ่งที่ดีกว่า

ความคิดของฉันคือว่าในขณะที่ใช้ท่อเราจะใช้กระบวนการพิเศษและท่อขณะที่อยู่ในที่นี่สตริงเพียงอธิบายไฟล์จะถูกนำมาใช้กับechocat

คำตอบ:


17

ไพพ์เป็นไฟล์ที่เปิดในระบบไฟล์ในเคอร์เนลและไม่สามารถเข้าถึงได้เหมือนไฟล์ปกติบนดิสก์ มันจะถูกบัฟเฟอร์โดยอัตโนมัติเฉพาะกับขนาดที่กำหนดและในที่สุดจะปิดกั้นเมื่อเต็ม ซึ่งแตกต่างจากไฟล์ที่มาจากอุปกรณ์บล็อกท่อมีพฤติกรรมเหมือนอุปกรณ์ตัวอักษรดังนั้นโดยทั่วไปแล้วไม่รองรับlseek()และข้อมูลที่อ่านจากพวกมันจะไม่สามารถอ่านได้อีกเหมือนที่คุณทำกับไฟล์ปกติ

สตริงที่นี่เป็นไฟล์ปกติที่สร้างขึ้นในระบบไฟล์ที่เมาท์ เชลล์สร้างไฟล์และเก็บ descriptor ไว้ในขณะที่ลบลิงค์ระบบไฟล์ออกทันที(และทำการลบ)ก่อนที่มันจะเขียน / อ่านไบต์ไปยัง / จากไฟล์ เคอร์เนลจะรักษาพื้นที่ที่จำเป็นสำหรับไฟล์ไว้จนกว่ากระบวนการทั้งหมดจะปล่อยตัวอธิบายทั้งหมด หากเด็กที่อ่านจาก descriptor ดังกล่าวมีความสามารถในการทำเช่นนั้นก็สามารถ rewound ด้วยlseek()และอ่านอีกครั้ง

ในทั้งสองกรณีโทเค็น<<<และ|เป็นตัวแทนของ file-descriptors และไม่จำเป็นต้องใช้ไฟล์เอง คุณสามารถรับความคิดที่ดีขึ้นของสิ่งที่เกิดขึ้นโดยทำสิ่งที่ชอบ:

readlink /dev/fd/1 | cat

...หรือ...

ls -l <<<'' /dev/fd/*

ความแตกต่างที่สำคัญที่สุดระหว่างสองไฟล์คือ here-string / doc นั้นค่อนข้างเป็นเรื่องที่เกิดขึ้นครั้งเดียว - เชลล์เขียนข้อมูลทั้งหมดลงไปก่อนที่จะเสนอ descriptor การอ่านให้กับเด็ก ในอีกทางหนึ่งเชลล์จะเปิดไพพ์บน descriptor ที่เหมาะสมและห้ามให้เด็กจัดการกับไพพ์นั้น - ดังนั้นจึงถูกเขียน / อ่านพร้อมกันที่ปลายทั้งสอง

อย่างไรก็ตามความแตกต่างเหล่านี้เป็นจริงโดยทั่วไปเท่านั้น เท่าที่ผมทราบ(ซึ่งไม่ได้จริงๆสิ่งที่ไกล)นี่คือความจริงของสวยมากเปลือกซึ่งจัดการทุก<<<ที่นี่สตริงสั้นมือสำหรับการเปลี่ยนเส้นทางที่นี่เอกสารมีข้อยกเว้นเดียวของ<< , , และอื่น ๆ ที่สายพันธุ์ที่มีแนวโน้มที่จะกลับมาที่นี่-เอกสารที่มีท่อแม้ว่าและดังนั้นในเปลือกหอยเหล่านั้นมีจริงๆคือความแตกต่างน้อยมากระหว่างสองหลังจากทั้งหมดyashyashbusyboxdashash

ตกลง - สองข้อยกเว้น ตอนนี้ฉันกำลังคิดเกี่ยวกับมันksh93ไม่ได้ทำไปป์ไลน์เลย|แต่แทนที่จะจัดการกับซ็อกเก็ตธุรกิจทั้งหมด - แม้ว่ามันจะทำไฟล์ tmp ที่ถูกลบสำหรับ<<<*คนอื่น ๆ ยิ่งไปกว่านั้นมันวางเฉพาะส่วนแยกของไปป์ไลน์ในสภาพแวดล้อม subshellซึ่งเป็นประเภทของ POSIX Euphemism อย่างน้อยก็ทำหน้าที่เหมือน subshellและดังนั้นแม้จะไม่ทำส้อม

ความจริงก็คือผลลัพธ์ของมาตรฐาน @ PSkocik (ซึ่งมีประโยชน์มาก)ที่นี่อาจแตกต่างกันอย่างกว้างขวางด้วยเหตุผลหลายประการและส่วนใหญ่ขึ้นอยู่กับการใช้งาน สำหรับการตั้งค่าเอกสารที่นี่ปัจจัยที่สำคัญที่สุดคือ${TMPDIR}ประเภทของระบบไฟล์เป้าหมายและการกำหนดค่าแคช / ความพร้อมใช้งานในปัจจุบันและยังคงปริมาณข้อมูลที่จะเขียน สำหรับไพพ์จะเป็นขนาดของกระบวนการเชลล์เองเนื่องจากการทำสำเนาจะทำสำหรับส้อมที่ต้องการ ด้วยวิธีนี้bashมันแย่มากที่การตั้งค่าไปป์ไลน์(รวมการแทนที่$(คำสั่ง)) - เพราะมันใหญ่และช้ามากแต่ด้วยความที่ksh93แทบจะไม่แตกต่างกันเลย

นี่เป็นอีกตัวอย่างกระสุนเล็ก ๆ น้อย ๆ ที่แสดงให้เห็นว่าเชลล์แยกย่อย subshells สำหรับไพพ์ไลน์อย่างไร:

pipe_who(){ echo "$$"; sh -c 'echo "$PPID"'; }
pipe_who
pipe_who | { pipe_who | cat /dev/fd/3 -; } 3<&0

32059  #bash's pid
32059  #sh's ppid
32059  #1st subshell's $$
32111  #1st subshell sh's ppid
32059  #2cd subshell's $$
32114  #2cd subshell sh's ppid

ความแตกต่างระหว่างสิ่งที่pipe_who()รายงานการโทรไปป์ไลน์และรายงานของการรันหนึ่งครั้งในเชลล์ปัจจุบันเกิดจากพฤติกรรมที่ระบุ(ของ subshell ใน)การอ้างสิทธิ์ pid ของเชลล์พาเรนต์$$เมื่อขยาย แม้ว่าbashsubshells เป็นกระบวนการที่แยกจากกันแน่นอนแต่$$พารามิเตอร์เชลล์พิเศษไม่ใช่แหล่งข้อมูลที่เชื่อถือได้ของข้อมูลนี้ ยังเด็ก subshell ของเปลือกไม่ได้ปฏิเสธที่จะรายงานได้อย่างถูกต้องของมันsh$PPID


มีประโยชน์มาก ระบบไฟล์ในเคอร์เนลมีชื่อหรือไม่? มันหมายความว่ามันมีอยู่ในพื้นที่เคอร์เนล?
utlamn

2
@utlamn - จริงใช่ - เพียงpipefs มันทั้งหมดในเคอร์เนล - แต่(นอกเหนือจากสิ่งที่ชอบ FUSE)เพื่อให้เป็นทุกไฟล์ I / O
mikeserv

10

ไม่มีสิ่งใดทดแทนการเปรียบเทียบ:

pskocik@ProBook:~ 
$ time (for((i=0;i<1000;i++)); do cat<<< foo >/dev/null; done  )

real    0m2.080s
user    0m0.738s
sys 0m1.439s
pskocik@ProBook:~ 
$ time (for((i=0;i<1000;i++)); do echo foo |cat >/dev/null; done  )

real    0m4.432s
user    0m2.095s
sys 0m3.927s
$ time (for((i=0;i<1000;i++)); do cat <(echo foo) >/dev/null; done  )
real    0m3.380s
user    0m1.121s
sys 0m3.423s

และสำหรับข้อมูลจำนวนมากขึ้น:

TENMEG=$(ruby -e 'puts "A"*(10*1024*1024)')
pskocik@ProBook:~ 
$ time (for((i=0;i<100;i++)); do echo "$TENMEG" |cat >/dev/null; done  )

real    0m42.327s
user    0m38.591s
sys 0m4.226s
pskocik@ProBook:~ 
$ time (for((i=0;i<100;i++)); do cat<<< "$TENMEG" >/dev/null; done  )

real    1m26.946s
user    1m23.116s
sys 0m3.681s
pskocik@ProBook:~ 

$ time (for((i=0;i<100;i++)); do cat <(echo "$TENMEG") >/dev/null; done  )

real    0m43.910s
user    0m40.178s
sys 0m4.119s

ดูเหมือนว่ารุ่นท่อจะมีค่าใช้จ่ายในการติดตั้งที่มากขึ้น แต่ในที่สุดก็มีประสิทธิภาพมากกว่า


@mikeserv นั่นถูกต้องแล้ว ฉันเพิ่มมาตรฐานที่มีข้อมูลจำนวนมากขึ้น
PSkocik

2
echo foo >/dev/shm/1;cat /dev/shm/1 >/dev/nullดูเหมือนจะรวดเร็วทั้งสองกรณี ...
user23013

@ user23013 นั่นสมเหตุสมผล ฉันไม่เห็นสาเหตุecho "$longstring"หรืออย่างใดอย่างหนึ่ง<<<"$longstring"tweaked สำหรับประสิทธิภาพและด้วยสายสั้นประสิทธิภาพไม่สำคัญมากอยู่แล้ว
PSkocik

เป็นที่น่าสนใจว่าในกรณีของฉัน (บน Ubuntu 14.04, Intel i7 Quad Core) จะเร็วกว่าcat <(echo foo) >/dev/null echo foo | cat >/dev/null
pabouk

1
@Prem ใช่นั่นเป็นวิธีที่ดีกว่า แต่สิ่งที่ดีกว่าคือไม่ต้องกังวลเกี่ยวกับเรื่องนี้เลยและใช้เครื่องมือที่เหมาะสมสำหรับงาน ไม่มีเหตุผลที่จะคิดว่า heredocs จะถูกปรับตามประสิทธิภาพ
PSkocik
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.