ทำไมการวนซ้ำไฟล์เร็วกว่าการอ่านลงในหน่วยความจำและการคำนวณสองครั้งทำไม


26

ฉันกำลังเปรียบเทียบสิ่งต่อไปนี้

tail -n 1000000 stdout.log | grep -c '"success": true'
tail -n 1000000 stdout.log | grep -c '"success": false'

ด้วยดังต่อไปนี้

log=$(tail -n 1000000 stdout.log)
echo "$log" | grep -c '"success": true'
echo "$log" | grep -c '"success": false'

และน่าประหลาดใจที่สองใช้เวลานานกว่าครั้งแรกเกือบ 3 เท่า มันควรจะเร็วกว่านี้ใช่ไหม


อาจเป็นเพราะโซลูชันที่สองเนื้อหาไฟล์ถูกอ่าน 3 ครั้งและมีเพียงสองครั้งในตัวอย่างแรก
Laurent C.

4
อย่างน้อยในตัวอย่างที่สองคุณ$( command substitution )จะไม่ถูกสตรีม ส่วนที่เหลือทั้งหมดเกิดขึ้นผ่านไปป์พร้อมกัน แต่ในตัวอย่างที่สองคุณต้องรอlog=ให้เสร็จสมบูรณ์ ลองใช้ด้วย << HERE \ n $ {log = $ (command)} \ nHERE - ดูสิ่งที่คุณได้รับ
mikeserv

ในกรณีของไฟล์ที่มีขนาดใหญ่มากเครื่องที่ จำกัด หน่วยความจำหรือไอเท็มอื่น ๆgrepสำหรับคุณอาจเห็นการเร่งความเร็วโดยใช้teeดังนั้นไฟล์จะอ่านได้เพียงครั้งเดียว cat stdout.log | tee >/dev/null >(grep -c 'true'>true.cnt) >(grep -c 'false'>false.cnt); cat true.cnt; cat false.cnt
แมตต์

@ LaurentC. ไม่มันอ่านได้เพียงครั้งเดียวในตัวอย่างที่สอง มีเพียงหนึ่งการเรียกเพื่อหาง
psusi

ตอนนี้เปรียบเทียบสิ่งเหล่านี้กับtail -n 10000 | fgrep -c '"success": true'และเท็จ
kojiro

คำตอบ:


11

ในมือข้างหนึ่งวิธีแรกเรียกtailสองครั้งดังนั้นจึงต้องทำงานมากกว่าวิธีที่สองซึ่งทำได้เพียงครั้งเดียว บนมืออื่น ๆ วิธีที่สองมีการคัดลอกข้อมูลลงในเปลือกแล้วกลับออกมาจึงมีการทำงานมากขึ้นกว่ารุ่นแรกที่เป็นประปาโดยตรงในtail grepวิธีแรกมีประโยชน์พิเศษบนเครื่องประมวลผลแบบหลาย: grepสามารถทำงานในแบบคู่ขนานกับtailในขณะที่วิธีการที่สองเนื่องอย่างเคร่งครัดแรกแล้วtailgrep

ดังนั้นจึงไม่มีเหตุผลที่ชัดเจนว่าทำไมคนหนึ่งควรจะเร็วกว่าอีกคนหนึ่ง

หากคุณต้องการดูว่าเกิดอะไรขึ้นดูว่าระบบเรียกเชลล์ทำอะไร ลองด้วยกระสุนที่แตกต่างกันเช่นกัน

strace -t -f -o 1.strace sh -c '
  tail -n 1000000 stdout.log | grep "\"success\": true" | wc -l;
  tail -n 1000000 stdout.log | grep "\"success\": false" | wc -l'

strace -t -f -o 2-bash.strace bash -c '
  log=$(tail -n 1000000 stdout.log);
  echo "$log" | grep "\"success\": true" | wc -l;
  echo "$log" | grep "\"success\": true" | wc -l'

strace -t -f -o 2-zsh.strace zsh -c '
  log=$(tail -n 1000000 stdout.log);
  echo "$log" | grep "\"success\": true" | wc -l;
  echo "$log" | grep "\"success\": true" | wc -l'

ด้วยวิธีที่ 1 ขั้นตอนหลักคือ:

  1. tail อ่านและค้นหาเพื่อหาจุดเริ่มต้น
  2. tailเขียนชิ้น 4096- ไบต์ที่grepอ่านเร็วที่สุดเท่าที่พวกเขาผลิต
  3. ทำซ้ำขั้นตอนก่อนหน้าสำหรับสตริงการค้นหาที่สอง

ด้วยวิธีที่ 2 ขั้นตอนหลักคือ:

  1. tail อ่านและค้นหาเพื่อหาจุดเริ่มต้น
  2. tail เขียนชิ้น 4096- ไบต์ซึ่งทุบตีอ่าน 128 ไบต์ในแต่ละครั้งและ zsh อ่าน 4096 ไบต์ในแต่ละครั้ง
  3. Bash หรือ zsh เขียนชิ้น 4096- ไบต์ที่grepอ่านเร็วที่สุดเท่าที่พวกเขากำลังผลิต
  4. ทำซ้ำขั้นตอนก่อนหน้าสำหรับสตริงการค้นหาที่สอง

ชิ้นงานขนาด 128 ไบต์ของ Bash เมื่ออ่านผลลัพธ์ของการทดแทนคำสั่งทำให้ช้าลงอย่างเห็นได้ชัด zsh ออกมาเร็วพอ ๆ กับวิธีที่ 1 สำหรับฉัน ระยะของคุณอาจแตกต่างกันไปขึ้นอยู่กับประเภทและหมายเลขของ CPU การกำหนดตารางเวลาเวอร์ชันของเครื่องมือที่เกี่ยวข้องและขนาดของข้อมูล


รูปหน้าขนาด 4k นั้นขึ้นอยู่กับอะไรหรือไม่ ฉันหมายถึงหางและ zsh เป็นเพียงแค่การสร้างตึกระฟ้าหรือไม่ (อาจเป็นคำศัพท์ที่ไม่ถูกต้อง แต่ฉันหวังว่าจะไม่ ... ) การทุบตีทำอะไรผิดไป
mikeserv

นี่คือจุดที่ Gilles! ด้วย zsh วิธีที่สองจะเร็วขึ้นเล็กน้อยบนเครื่องของฉัน
phunehehe

ผลงานยอดเยี่ยม Gilles, tks
X Tian

@mikeserv ฉันไม่ได้ดูซอร์สเพื่อดูว่าโปรแกรมเหล่านี้เลือกขนาดอย่างไร สาเหตุที่เป็นไปได้มากที่สุดที่จะเห็น 4096 อาจเป็นค่าคงที่ในตัวหรือst_blksizeค่าของไพพ์ซึ่งเป็น 4096 ในเครื่องนี้ (และฉันไม่รู้ว่าเป็นเพราะขนาดหน้า MMU) หรือไม่ Bash's 128 จะต้องมีค่าคงที่ในตัว
Gilles 'หยุดความชั่วร้าย'

@Gilles ขอบคุณสำหรับคำตอบที่มีน้ำใจ ฉันเพิ่งสงสัยเกี่ยวกับขนาดหน้าเมื่อเร็ว ๆ นี้
mikeserv

26

ฉันได้ทำการทดสอบต่อไปนี้แล้วและในระบบของฉันความแตกต่างที่ได้นั้นยาวกว่าสำหรับสคริปต์ที่สองประมาณ 100 เท่า

ไฟล์ของฉันเป็นเอาต์พุตแบบ strace ที่เรียกว่า bigfile

$ wc -l bigfile.log 
1617000 bigfile.log

สคริป

xtian@clafujiu:~/tmp$ cat p1.sh
tail -n 1000000 bigfile.log | grep '"success": true' | wc -l
tail -n 1000000 bigfile.log | grep '"success": false' | wc -l

xtian@clafujiu:~/tmp$ cat p2.sh
log=$(tail -n 1000000 bigfile.log)
echo "$log" | grep '"success": true' | wc -l
echo "$log" | grep '"success": true' | wc -l

จริง ๆ แล้วฉันไม่มีแมทช์ใด ๆ สำหรับ grep ดังนั้นจึงไม่มีสิ่งใดถูกเขียนไปยังไปป์สุดท้ายผ่านไปยัง wc -l

นี่คือการกำหนดเวลา:

xtian@clafujiu:~/tmp$ time bash p1.sh
0
0

real    0m0.381s
user    0m0.248s
sys 0m0.280s
xtian@clafujiu:~/tmp$ time bash p2.sh
0
0

real    0m46.060s
user    0m43.903s
sys 0m2.176s

ดังนั้นฉันจึงรันสคริปต์ทั้งสองอีกครั้งผ่านคำสั่ง strace

strace -cfo p1.strace bash p1.sh
strace -cfo p2.strace bash p2.sh

นี่คือผลลัพธ์จากการติดตาม:

$ cat p1.strace 
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 97.24    0.508109       63514         8         2 waitpid
  1.61    0.008388           0     84569           read
  1.08    0.005659           0     42448           write
  0.06    0.000328           0     21233           _llseek
  0.00    0.000024           0       204       146 stat64
  0.00    0.000017           0       137           fstat64
  0.00    0.000000           0       283       149 open
  0.00    0.000000           0       180         8 close
...
  0.00    0.000000           0       162           mmap2
  0.00    0.000000           0        29           getuid32
  0.00    0.000000           0        29           getgid32
  0.00    0.000000           0        29           geteuid32
  0.00    0.000000           0        29           getegid32
  0.00    0.000000           0         3         1 fcntl64
  0.00    0.000000           0         7           set_thread_area
------ ----------- ----------- --------- --------- ----------------
100.00    0.522525                149618       332 total

และ p2.strace

$ cat p2.strace 
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 75.27    1.336886      133689        10         3 waitpid
 13.36    0.237266          11     21231           write
  4.65    0.082527        1115        74           brk
  2.48    0.044000        7333         6           execve
  2.31    0.040998        5857         7           clone
  1.91    0.033965           0    705681           read
  0.02    0.000376           0     10619           _llseek
  0.00    0.000000           0       248       132 open
...
  0.00    0.000000           0       141           mmap2
  0.00    0.000000           0       176       126 stat64
  0.00    0.000000           0       118           fstat64
  0.00    0.000000           0        25           getuid32
  0.00    0.000000           0        25           getgid32
  0.00    0.000000           0        25           geteuid32
  0.00    0.000000           0        25           getegid32
  0.00    0.000000           0         3         1 fcntl64
  0.00    0.000000           0         6           set_thread_area
------ ----------- ----------- --------- --------- ----------------
100.00    1.776018                738827       293 total

การวิเคราะห์

ไม่น่าแปลกใจที่ทั้งสองกรณีส่วนใหญ่ใช้เวลาในการรอให้กระบวนการเสร็จสมบูรณ์ แต่ p2 รอนานกว่า p1 2.63 เท่าและอย่างที่คนอื่นพูดถึงคุณจะเริ่มช้าใน p2.sh

ดังนั้นตอนนี้ลืมเกี่ยวกับการwaitpidไม่สนใจ%คอลัมน์และดูที่คอลัมน์วินาทีในร่องรอยทั้งสอง

เวลาที่ใหญ่ที่สุด p1 ใช้เวลาส่วนใหญ่ในการอ่านอาจจะเข้าใจได้เนื่องจากมีไฟล์ขนาดใหญ่ให้อ่าน แต่ p2 ใช้เวลาอ่านนานกว่า 28.82 เท่าที่ p1 อ่าน - bashไม่ได้คาดหวังว่าจะอ่านไฟล์ขนาดใหญ่เช่นนี้เป็นตัวแปรและอาจอ่านบัฟเฟอร์ในแต่ละครั้งโดยแยกเป็นบรรทัดแล้วจึงรับอีกไฟล์

จำนวนการอ่าน p2 คือ 705k เทียบกับ 84k สำหรับ p1 แต่ละการอ่านที่ต้องการสลับบริบทไปยังพื้นที่เคอร์เนลและออกอีกครั้ง เกือบ 10 เท่าของจำนวนการอ่านและการสลับบริบท

เวลาในการเขียน p2 ใช้เวลานานในการเขียนมากกว่า p1 41.93

จำนวนการเขียน p1 นั้นเขียนได้มากกว่า p2, 42k และ 21k แต่จะเร็วกว่ามาก

อาจเป็นเพราะของechoบรรทัดเข้าgrepเทียบกับบัฟเฟอร์การเขียนหาง

ยิ่งไปกว่านั้น p2 ใช้เวลาในการเขียนมากกว่าที่เป็นอยู่ในการอ่าน p1 คือวิธีอื่น ๆ !

ปัจจัยอื่น ๆดูที่จำนวนการbrkโทรของระบบ: p2 ใช้เวลา 2.42 เท่าในการทำลายมากกว่าที่อ่าน! ใน p1 (มันไม่ได้ลงทะเบียน) brkคือเมื่อโปรแกรมต้องการขยายพื้นที่ที่อยู่ของมันเพราะไม่ได้จัดสรรเพียงพอในตอนแรกอาจเป็นเพราะการทุบตีต้องอ่านไฟล์นั้นลงในตัวแปรและไม่คาดหวังว่ามันจะมีขนาดใหญ่และตามที่ @scai พูดถึงถ้า ไฟล์ใหญ่เกินไปแม้ว่ามันจะไม่ทำงาน

tailอาจเป็นตัวอ่านไฟล์ที่มีประสิทธิภาพทีเดียวเพราะนี่คือสิ่งที่มันถูกออกแบบมาเพื่อทำมันอาจจะทำการ memmaps ไฟล์และสแกนหาตัวแบ่งบรรทัดดังนั้นจึงทำให้เคอร์เนลเพิ่มประสิทธิภาพ i / o ทุบตีไม่ดีทั้งเวลาที่ใช้ในการอ่านและเขียน

p2 ใช้เวลา 44ms และ 41ms ในcloneและexecvมันไม่ใช่จำนวนที่วัดได้สำหรับ p1 อาจทุบตีการอ่านและสร้างตัวแปรจากหาง

ในที่สุด Totals p1 จะเรียกระบบ ~ 150k เทียบกับ p2 740k (ยิ่งใหญ่กว่า 4.93 เท่า)

การกำจัด Waitpid นั้น p1 ใช้เวลา 0.014416 วินาทีในการดำเนินการเรียกระบบ, p2 0.439132 วินาที (อีก 30 ครั้ง)

ดังนั้นจึงปรากฏว่า p2 ใช้เวลาส่วนใหญ่ในพื้นที่ของผู้ใช้โดยไม่ทำอะไรเลยนอกจากรอให้การเรียกของระบบเสร็จสมบูรณ์และเคอร์เนลเพื่อจัดระเบียบหน่วยความจำใหม่ p1 ทำการเขียนมากขึ้น แต่มีประสิทธิภาพมากขึ้นและทำให้โหลดระบบน้อยลง

ข้อสรุป

ฉันจะไม่พยายามกังวลเกี่ยวกับการเขียนโปรแกรมผ่านหน่วยความจำเมื่อเขียนสคริปต์ทุบตีซึ่งไม่ได้หมายความว่าคุณจะไม่พยายามอย่างมีประสิทธิภาพ

tailถูกออกแบบมาเพื่อทำสิ่งที่มันอาจmemory mapsเป็นไฟล์เพื่อให้มีประสิทธิภาพในการอ่านและช่วยให้เคอร์เนลเพื่อเพิ่มประสิทธิภาพ i / o

วิธีที่ดีกว่าในการเพิ่มประสิทธิภาพปัญหาของคุณอาจเป็นอันดับแรกgrepสำหรับ "" ความสำเร็จ ": 'บรรทัดจากนั้นนับความจริงและเท็จgrepมีตัวเลือกการนับที่หลีกเลี่ยงwc -lหรือยังดีกว่าไปป์หางผ่านไปawkและนับความจริงและ เท็จพร้อมกัน p2 ไม่เพียง แต่ใช้เวลานาน แต่เพิ่มภาระให้กับระบบในขณะที่หน่วยความจำกำลังถูกสับด้วย brks


2
TL; DR: malloc (); หากคุณสามารถบอก $ log ว่าต้องใช้เงินเท่าใดและสามารถเขียนได้อย่างรวดเร็วในหนึ่งครั้งโดยไม่มีการจัดสรรใหม่อาจเป็นไปได้อย่างรวดเร็ว
Chris K

5

จริงๆแล้ววิธีแก้ปัญหาแรกอ่านไฟล์ในหน่วยความจำด้วย สิ่งนี้เรียกว่าการแคชและระบบปฏิบัติการทำโดยอัตโนมัติ

และเป็นอยู่แล้วได้อย่างถูกต้องอธิบายโดยmikeserv exectutes โซลูชั่นแรกgrep ในขณะที่ไฟล์จะถูกอ่านในขณะที่การแก้ปัญหาที่สองรันหลังจากtailที่ไฟล์ถูกอ่านโดย

ดังนั้นทางออกแรกนั้นเร็วกว่าเนื่องจากมีการปรับให้เหมาะสมต่าง ๆ แต่สิ่งนี้ไม่จำเป็นต้องเป็นจริงเสมอไป สำหรับไฟล์ที่มีขนาดใหญ่มากซึ่งระบบปฏิบัติการตัดสินใจไม่แคชโซลูชันที่สองอาจทำงานได้เร็ว แต่โปรดทราบว่าสำหรับไฟล์ที่มีขนาดใหญ่กว่าซึ่งไม่พอดีกับหน่วยความจำของคุณโซลูชันที่สองจะไม่ทำงานเลย


3

ฉันคิดว่าความแตกต่างหลักนั้นง่ายมากที่echoจะช้า พิจารณาสิ่งนี้:

$ time (tail -n 1000000 foo | grep 'true' | wc -l; 
        tail -n 1000000 foo | grep 'false' | wc -l;)
666666
333333

real    0m0.999s
user    0m1.056s
sys     0m0.136s

$ time (log=$(tail -n 1000000 foo); echo "$log" | grep 'true' | wc -l; 
                                    echo "$log" | grep 'false' | wc -l)
666666
333333

real    0m4.132s
user    0m3.876s
sys     0m0.468s

$ time (tail -n 1000000 foo > bb;  grep 'true' bb | wc -l; 
                                   grep 'false' bb | wc -l)
666666
333333

real    0m0.568s
user    0m0.512s
sys     0m0.092s

อย่างที่คุณเห็นข้างต้นขั้นตอนที่ใช้เวลานานคือการพิมพ์ข้อมูล หากคุณเพียงแค่เปลี่ยนเส้นทางไปยังไฟล์ใหม่และ grep ผ่านมันจะเร็วขึ้นมากเมื่ออ่านไฟล์เพียงครั้งเดียว


และตามที่ร้องขอด้วยสตริงที่นี่:

 $ time (log=$(tail -n 1000000 foo); grep 'true' <<< $log | wc -l; 
                                     grep 'false' <<< $log | wc -l  )
1
1

real    0m7.574s
user    0m7.092s
sys     0m0.516s

อันนี้ช้าลงอย่างน่าจะเป็นเพราะสตริงที่นี่เชื่อมต่อข้อมูลทั้งหมดเข้ากับสายยาวหนึ่งเส้นและจะทำให้ช้าลงgrep:

$ tail -n 1000000 foo | (time grep -c 'true')
666666

real    0m0.500s
user    0m0.472s
sys     0m0.000s

$ tail -n 1000000 foo | perl -pe 's/\n/ /' | (time grep -c 'true')
1

real    0m1.053s
user    0m0.048s
sys     0m0.068s

หากตัวแปรถูกยกมาเพื่อไม่ให้เกิดการแยกสิ่งต่าง ๆ จะเร็วขึ้นเล็กน้อย:

 $ time (log=$(tail -n 1000000 foo); grep 'true' <<< "$log" | wc -l; 
                                     grep 'false' <<< "$log" | wc -l  )
666666
333333

real    0m6.545s
user    0m6.060s
sys     0m0.548s

แต่ก็ยังช้าเพราะขั้นตอนการ จำกัด อัตรากำลังพิมพ์ข้อมูล


ทำไมคุณไม่ลอง<<<มันจะน่าสนใจเพื่อดูว่ามันสร้างความแตกต่าง
แกรม

3

ฉันเคยไปที่นี้เช่นกัน ... ก่อนอื่นฉันสร้างไฟล์:

printf '"success": "true"
        "success": "true"
        "success": "false"
        %.0b' `seq 1 500000` >|/tmp/log

หากคุณทำงานด้านบนตัวคุณเองคุณจะมากับ 1.5million เส้นใน/tmp/logกับอัตราส่วน 2: 1 ของ"success": "true"สายที่จะ"success": "false"สาย

สิ่งต่อไปที่ฉันทำคือทำการทดสอบ ฉันทำการทดสอบทั้งหมดผ่านทางพร็อกซีshดังนั้นtimeจะต้องดูกระบวนการเดียวเท่านั้นและอาจแสดงผลลัพธ์เดียวสำหรับงานทั้งหมด

ดูเหมือนว่าจะเร็วที่สุดแม้ว่ามันจะเพิ่มตัวอธิบายไฟล์ที่สองและtee,แม้ว่าฉันคิดว่าฉันสามารถอธิบายได้ว่าทำไม:

    time sh <<-\CMD
        . <<HD /dev/stdin | grep '"success": "true"' | wc -l
            tail -n 1000000 /tmp/log | { tee /dev/fd/3 |\
                grep '"success": "false"' |\
                    wc -l 1>&2 & } 3>&1 &
        HD
    CMD
666666
333334
sh <<<''  0.11s user 0.08s system 84% cpu 0.224 total

นี่เป็นครั้งแรกของคุณ:

    time sh <<\CMD
        tail -n 1000000 /tmp/log | grep '"success": "true"' | wc -l
        tail -n 1000000 /tmp/log | grep '"success": "false"' | wc -l
    CMD

666666
333334
sh <<<''  0.31s user 0.17s system 148% cpu 0.323 total

และครั้งที่สองของคุณ:

    time sh <<\CMD
        log=$(tail -n 1000000 /tmp/log)
        echo "$log" | grep '"success": "true"' | wc -l
        echo "$log" | grep '"success": "false"' | wc -l
    CMD
666666
333334
sh <<<''  2.12s user 0.46s system 108% cpu 2.381 total

คุณจะเห็นว่าในการทดสอบของฉันมีความเร็วมากกว่า 3 * แตกต่างกันเมื่ออ่านมันเป็นตัวแปรตามที่คุณทำ

ฉันคิดว่าส่วนหนึ่งคือตัวแปรเชลล์จะต้องถูกแยกและจัดการโดยเชลล์เมื่อมันถูกอ่าน - ไม่ใช่ไฟล์

here-documentในมืออื่น ๆ สำหรับ intents และวัตถุประสงค์เป็นfile- เป็นfile descriptor,แล้วล่ะค่ะ และอย่างที่เรารู้ - Unix ทำงานกับไฟล์ได้

สิ่งที่น่าสนใจที่สุดสำหรับฉันhere-docsคือคุณสามารถจัดการกับพวกเขาfile-descriptors- แบบตรง|pipe-และดำเนินการได้ สิ่งนี้มีประโยชน์มากเพราะช่วยให้คุณมีอิสระมากขึ้นในการชี้ตำแหน่งของ|pipeคุณ

ผมต้องเพราะเป็นครั้งแรกที่กินและมีอะไรเหลือที่สองในการอ่านเป็น แต่เนื่องจากฉันเข้ามาและหยิบมันขึ้นมาอีกครั้งเพื่อผ่านไปมันก็ไม่สำคัญอะไร ถ้าคุณใช้คนอื่น ๆ แนะนำ:teetailgrephere-doc |pipe|piped/dev/fd/3>&1 stdout,grep -c

    time sh <<-\CMD
        . <<HD /dev/stdin | grep -c '"success": "true"'
            tail -n 1000000 /tmp/log | { tee /dev/fd/3 |\
                grep -c '"success": "false"' 1>&2 & } 3>&1 &
        HD
    CMD
666666
333334
sh <<<''  0.07s user 0.04s system 62% cpu 0.175 total

มันเร็วยิ่งขึ้น

แต่เมื่อฉันเรียกใช้โดย. sourcingที่heredocฉันไม่สามารถทำแบ็คกราวน์ของกระบวนการแรกให้รันอย่างสมบูรณ์พร้อมกันได้ ที่นี่มันไม่มีพื้นหลังเต็มที่:

    time sh <<\CMD
        tail -n 1000000 /tmp/log | { tee /dev/fd/3 |\
            grep -c '"success": "true"' 1>&2 & } 3>&1 |\
                grep -c '"success": "false"'
    CMD
666666
333334
sh <<<''  0.10s user 0.08s system 109% cpu 0.165 total

แต่เมื่อฉันเพิ่ม &:

    time sh <<\CMD
        tail -n 1000000 /tmp/log | { tee /dev/fd/3 |\
            grep -c '"success": "true"' 1>&2 & } 3>&1 & |\
                grep -c '"success": "false"'
    CMD
sh: line 2: syntax error near unexpected token `|'

แต่ถึงกระนั้นความแตกต่างก็ดูเหมือนจะเพียงไม่กี่ร้อยวินาทีอย่างน้อยสำหรับฉันดังนั้นลองคิดดูสิ

อย่างไรก็ตามเหตุผลที่มันรันเร็วกว่าteeนั้นก็เพราะทั้งคู่grepsทำงานในเวลาเดียวกันโดยมีการเรียกใช้tail. teeซ้ำไฟล์เดียวสำหรับเราและแยกมันออกเป็นgrepกระบวนการที่สองในสตรีม - ทุกอย่างทำงานในทันทีตั้งแต่ต้นจนจบดังนั้นพวกเขาจึง ทั้งหมดเสร็จสิ้นในเวลาเดียวกันด้วย

ดังนั้นกลับไปที่ตัวอย่างแรกของคุณ:

    tail | grep | wc #wait til finished
    tail | grep | wc #now we're done

และครั้งที่สองของคุณ:

    var=$( tail ) ; #wait til finished
    echo | grep | wc #wait til finished
    echo | grep | wc #now we're done

แต่เมื่อเราแยกอินพุตและเรียกใช้กระบวนการของเราพร้อมกัน:

          3>&1  | grep #now we're done
              /        
    tail | tee  #both process together
              \  
          >&1   | grep #now we're done

1
+1 แต่การทดสอบครั้งสุดท้ายของคุณตายไปพร้อมกับข้อผิดพลาดทางไวยากรณ์, ฉันไม่คิดว่าเวลาที่ถูกต้องมี :)
terdon

@terdon พวกเขาอาจจะผิด - ฉันกำลังชี้ให้เห็นว่ามันตาย ฉันแสดงให้เห็นถึงความแตกต่างระหว่างแท็ก & กับ & & ไม่ & - เมื่อคุณเพิ่มมันเชลล์จะอารมณ์เสีย แต่ฉันคัดลอก / วางจำนวนมากดังนั้นฉันอาจจะเลอะหนึ่งหรือสอง แต่ฉันคิดว่าพวกเขากำลัง ...
mikeserv

sh: บรรทัดที่ 2: ข้อผิดพลาดทางไวยากรณ์ใกล้โทเค็นที่ไม่คาดคิด `| '
terdon

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