Tôi đã thực hiện bài kiểm tra sau và trên hệ thống của tôi, kết quả khác biệt dài hơn khoảng 100 lần cho tập lệnh thứ hai.
Tập tin của tôi là một đầu ra strace được gọi là bigfile
$ wc -l bigfile.log
1617000 bigfile.log
Chữ viết
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
Tôi thực sự không có bất kỳ trận đấu nào cho grep vì vậy không có gì được ghi vào đường ống cuối cùng thông qua wc -l
Dưới đây là thời gian:
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
Vì vậy, tôi đã chạy hai tập lệnh một lần nữa thông qua lệnh strace
strace -cfo p1.strace bash p1.sh
strace -cfo p2.strace bash p2.sh
Dưới đây là kết quả từ các dấu vết:
$ 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
Và 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
Phân tích
Không có gì đáng ngạc nhiên, trong cả hai trường hợp, phần lớn thời gian dành cho việc chờ đợi một quá trình hoàn thành, nhưng p2 chờ gấp 2,63 lần so với p1 và như những người khác đã đề cập, bạn đang bắt đầu muộn trong p2.sh.
Vì vậy, bây giờ hãy quên đi waitpid
, bỏ qua %
cột và nhìn vào cột giây trên cả hai dấu vết.
Thời gian lớn nhất p1 dành phần lớn thời gian để đọc có thể hiểu được, bởi vì có một tệp lớn để đọc, nhưng p2 dành thời gian đọc dài hơn 28,82 lần so với p1. - bash
không mong đợi để đọc một tệp lớn như vậy thành một biến và có thể đang đọc bộ đệm tại một thời điểm, chia thành các dòng và sau đó lấy một tệp khác.
số lần đọc p2 là 705k so với 84k cho p1, mỗi lần đọc yêu cầu chuyển ngữ cảnh vào không gian kernel và ra ngoài một lần nữa. Gần 10 lần số lần đọc và chuyển đổi ngữ cảnh.
Thời gian viết p2 dành thời gian viết dài hơn 41,93 lần so với p1
số đếm ghi p1 không viết nhiều hơn p2, 42k so với 21k, tuy nhiên chúng nhanh hơn nhiều.
Có lẽ là do các echo
dòng vào grep
trái ngược với bộ đệm viết đuôi.
Hơn nữa , p2 dành nhiều thời gian hơn cho việc viết hơn là đọc, p1 là cách khác!
Yếu tố khác Nhìn vào số lượng brk
cuộc gọi hệ thống: p2 dành thời gian ngắt gấp 2,42 lần so với số lần đọc! Trong p1 (nó thậm chí không đăng ký). brk
là khi chương trình cần mở rộng không gian địa chỉ của nó vì ban đầu không được phân bổ, điều này có thể là do bash phải đọc tệp đó vào biến và không mong đợi nó lớn như vậy, và như @scai đã đề cập, nếu tập tin trở nên quá lớn, thậm chí điều đó sẽ không hoạt động.
tail
có lẽ là một trình đọc tệp khá hiệu quả, bởi vì đây là những gì nó được thiết kế để làm, nó có thể ghi lại tệp và quét để ngắt dòng, do đó cho phép kernel tối ưu hóa i / o. Bash không tốt cả về thời gian đọc và viết.
p2 dành 44ms và 41ms clone
và execv
đó không phải là số tiền có thể đo được cho p1. Có lẽ bash đọc và tạo biến từ đuôi.
Cuối cùng, Totals p1 thực hiện các cuộc gọi hệ thống ~ 150k so với p2 740k (gấp 4,93 lần).
Loại bỏ Waitpid, p1 dành 0,014416 giây thực hiện các cuộc gọi hệ thống, p2 0,439132 giây (lâu hơn 30 lần).
Vì vậy, có vẻ như p2 dành phần lớn thời gian trong không gian người dùng, không làm gì ngoài việc chờ đợi các cuộc gọi hệ thống hoàn thành và nhân sắp xếp lại bộ nhớ, p1 thực hiện nhiều thao tác ghi hơn, nhưng hiệu quả hơn và giảm tải hệ thống đáng kể và do đó nhanh hơn.
Phần kết luận
Tôi sẽ không bao giờ cố gắng lo lắng về việc mã hóa thông qua bộ nhớ khi viết tập lệnh bash, điều đó không có nghĩa là bạn không cố gắng làm việc hiệu quả.
tail
được thiết kế để làm những gì nó làm, nó có thể memory maps
là tập tin sao cho hiệu quả để đọc và cho phép kernel tối ưu hóa i / o.
Cách tốt hơn để tối ưu hóa vấn đề của bạn có thể là trước tiên grep
cho các dòng '"thành công":' và sau đó đếm các dấu vết và sai lệch, grep
có một tùy chọn đếm một lần nữa để tránh wc -l
, hoặc thậm chí tốt hơn, chuyển đuôi qua awk
và đếm các dấu vết và sai lệch đồng thời. p2 không chỉ mất nhiều thời gian mà còn tăng tải cho hệ thống trong khi bộ nhớ bị xáo trộn với các brks.