ฉันได้ทำการทดสอบต่อไปนี้แล้วและในระบบของฉันความแตกต่างที่ได้นั้นยาวกว่าสำหรับสคริปต์ที่สองประมาณ 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