Tại sao mở tệp nhanh hơn đọc nội dung biến?


36

Trong một bashkịch bản tôi cần các giá trị khác nhau từ /proc/các tập tin. Cho đến bây giờ tôi có hàng tá dòng grepping trực tiếp như thế:

grep -oP '^MemFree: *\K[0-9]+' /proc/meminfo

Trong một nỗ lực để làm cho hiệu quả hơn, tôi đã lưu nội dung tệp trong một biến và ghi lại rằng:

a=$(</proc/meminfo)
echo "$a" | grep -oP '^MemFree: *\K[0-9]+'

Thay vì mở tệp nhiều lần, bạn chỉ nên mở một lần và grep nội dung biến, mà tôi giả sử sẽ nhanh hơn - nhưng thực tế thì nó chậm hơn:

bash 4.4.19 $ time for i in {1..1000};do grep ^MemFree /proc/meminfo;done >/dev/null
real    0m0.803s
user    0m0.619s
sys     0m0.232s
bash 4.4.19 $ a=$(</proc/meminfo)
bash 4.4.19 $ time for i in {1..1000};do echo "$a"|grep ^MemFree; done >/dev/null
real    0m1.182s
user    0m1.425s
sys     0m0.506s

Điều này cũng đúng với dashzsh. Tôi nghi ngờ trạng thái đặc biệt của /proc/các tệp là một lý do, nhưng khi tôi sao chép nội dung của /proc/meminfotệp thông thường và sử dụng thì kết quả là như nhau:

bash 4.4.19 $ cat </proc/meminfo >meminfo
bash 4.4.19 $ time for i in $(seq 1 1000);do grep ^MemFree meminfo; done >/dev/null
real    0m0.790s
user    0m0.608s
sys     0m0.227s

Sử dụng một chuỗi ở đây để lưu đường ống làm cho nó nhanh hơn một chút, nhưng vẫn không nhanh như với các tệp:

bash 4.4.19 $ time for i in $(seq 1 1000);do <<<"$a" grep ^MemFree; done >/dev/null
real    0m0.977s
user    0m0.758s
sys     0m0.268s

Tại sao mở tệp nhanh hơn đọc cùng một nội dung từ một biến?


@ l0b0 Giả định này không bị lỗi, câu hỏi cho thấy cách tôi đưa ra và câu trả lời giải thích tại sao lại như vậy. Chỉnh sửa của bạn bây giờ làm cho câu trả lời không trả lời câu hỏi tiêu đề nữa: Họ không nói liệu đó có phải là trường hợp không.
tráng miệng

OK, làm rõ. Bởi vì tiêu đề đã sai trong phần lớn các trường hợp, chỉ không cho bộ nhớ nhất định ánh xạ các tệp đặc biệt.
l0b0

@ l0b0 Không, đó là những gì tôi đang hỏi ở đây: “Tôi nghi ngờ tình trạng đặc biệt của /proc/các files dưới dạng lý do, nhưng khi tôi sao chép nội dung của /proc/meminfomột tập tin và thường xuyên sử dụng các kết quả đều giống nhau:” Thật là không đặc biệt để /proc/tập tin, đọc tập tin thường xuyên là nhanh hơn là tốt!
tráng miệng

Câu trả lời:


47

Ở đây, không phải là về việc mở một tệp so với việc đọc nội dung của một biến mà là về việc có nên bỏ qua một quy trình bổ sung hay không.

grep -oP '^MemFree: *\K[0-9]+' /proc/meminfocho phép một quá trình thực thi grepmở ra /proc/meminfo(một tệp ảo, trong bộ nhớ, không có I / O đĩa nào liên quan) đọc nó và khớp với biểu thức chính quy.

Phần đắt nhất trong đó là hủy quá trình và tải tiện ích grep và các phụ thuộc thư viện của nó, thực hiện liên kết động, mở cơ sở dữ liệu cục bộ, hàng tá tệp trên đĩa (nhưng có thể được lưu trong bộ nhớ).

Phần về đọc /proc/meminfolà không đáng kể so với, hạt nhân cần ít thời gian để tạo ra thông tin trong đó và grepcần ít thời gian để đọc nó.

Nếu bạn chạy strace -ctrên đó, bạn sẽ thấy các cuộc gọi một open()và một read()hệ thống được sử dụng để đọc /proc/meminfolà đậu phộng so với mọi thứ khác grepđể bắt đầu ( strace -ckhông tính việc giả mạo).

Trong:

a=$(</proc/meminfo)

Trong hầu hết các shell hỗ trợ $(<...)toán tử ksh đó , shell chỉ mở tệp và đọc nội dung của nó (và loại bỏ các ký tự dòng mới ở cuối). bashkhác biệt và kém hiệu quả hơn ở chỗ nó tạo ra một quy trình để thực hiện việc đọc và truyền dữ liệu cho cha mẹ thông qua một đường ống. Nhưng ở đây, nó được thực hiện một lần nên không thành vấn đề.

Trong:

printf '%s\n' "$a" | grep '^MemFree'

Shell cần sinh ra hai quá trình, chúng đang chạy đồng thời nhưng tương tác với nhau thông qua một đường ống. Việc tạo ra đường ống, phá bỏ, và viết và đọc từ nó có một ít chi phí. Chi phí lớn hơn nhiều là sinh sản của một quá trình bổ sung. Việc lập lịch trình của các quá trình cũng có một số tác động.

Bạn có thể thấy rằng việc sử dụng <<<toán tử zsh làm cho nó nhanh hơn một chút:

grep '^MemFree' <<< "$a"

Trong zsh và bash, điều đó được thực hiện bằng cách viết nội dung $atrong một tệp tạm thời, ít tốn kém hơn so với sinh ra một quy trình bổ sung, nhưng có lẽ sẽ không mang lại cho bạn bất kỳ lợi ích nào so với việc tắt dữ liệu /proc/meminfo. Điều đó vẫn kém hiệu quả hơn so với cách tiếp cận của bạn sao chép /proc/meminfotrên đĩa, vì việc ghi tệp tạm thời được thực hiện ở mỗi lần lặp.

dashkhông hỗ trợ các chuỗi ở đây, nhưng các chuỗi của nó được triển khai với một đường ống không liên quan đến việc sinh ra một quy trình bổ sung. Trong:

 grep '^MemFree' << EOF
 $a
 EOF

Vỏ tạo ra một đường ống, tạo ra một quá trình. Đứa trẻ thực hiện grepvới stdin của nó là đầu đọc của ống, và cha mẹ viết nội dung ở đầu kia của ống.

Nhưng việc xử lý đường ống và đồng bộ hóa quy trình vẫn có thể tốn kém hơn là chỉ lấy dữ liệu ra /proc/meminfo.

Nội dung của /proc/meminfonó ngắn và không mất nhiều thời gian để sản xuất. Nếu bạn muốn lưu một số chu kỳ CPU, bạn muốn loại bỏ các phần đắt tiền: bỏ qua các quy trình và chạy các lệnh bên ngoài.

Như:

IFS= read -rd '' meminfo < /proc/meminfo
memfree=${meminfo#*MemFree:}
memfree=${memfree%%$'\n'*}
memfree=${memfree#"${memfree%%[! ]*}"}

Tránh bashmặc dù có mô hình phù hợp là rất thiếu. Với zsh -o extendedglob, bạn có thể rút ngắn nó thành:

memfree=${${"$(</proc/meminfo)"##*MemFree: #}%%$'\n'*}

Lưu ý rằng ^đặc biệt trong nhiều shell (Bourne, fish, rc, es và zsh với tùy chọn Extendedglob ít nhất), tôi khuyên bạn nên trích dẫn nó. Cũng lưu ý rằng echokhông thể được sử dụng để xuất dữ liệu tùy ý (do đó tôi sử dụng printfở trên).


4
Trong trường hợp với printfbạn, shell cần sinh ra hai tiến trình, nhưng không phải printflà shell dựng sẵn?
David Conrad

6
@DavidConrad Đó là, nhưng hầu hết các shell không cố gắng phân tích đường ống cho phần nào nó có thể chạy trong quy trình hiện tại. Nó chỉ tự rèn và cho phép trẻ em tìm ra nó. Trong trường hợp này, quá trình cha mẹ rẽ nhánh hai lần; Đứa trẻ ở phía bên trái sau đó nhìn thấy một tích hợp và thực hiện nó; Đứa trẻ bên phải nhìn thấy grepvà hành quyết.
chepner

1
@DavidConrad, đường ống là một cơ chế IPC, vì vậy trong mọi trường hợp, hai bên sẽ phải chạy trong các quy trình khác nhau. Trong khi A | B, có một số shell như AT & T ksh hoặc zsh chạy Btrong quy trình shell hiện tại nếu đó là lệnh dựng sẵn hoặc ghép hoặc hàm, tôi không biết bất kỳ lệnh nào chạy Atrong quy trình hiện tại. Nếu có bất cứ điều gì, để làm điều đó, họ sẽ phải xử lý SIGPIPE một cách phức tạp như thể Ađang chạy trong tiến trình con và không chấm dứt lớp vỏ để hành vi không quá bất ngờ khi Bxuất hiện sớm. Nó dễ dàng hơn nhiều để chạy Btrong quá trình cha mẹ.
Stéphane Chazelas

Bash hỗ trợ<<<
D. Ben Knoble

1
@ D.BenKnoble, tôi không có ý ám bashchỉ không hỗ trợ <<<, chỉ là nhà điều hành đến từ zshgiống như $(<...)đến từ ksh.
Stéphane Chazelas

6

Trong trường hợp đầu tiên của bạn, bạn chỉ sử dụng tiện ích grep và tìm thứ gì đó từ tệp /proc/meminfo, /proclà một hệ thống tệp ảo để /proc/meminfotệp nằm trong bộ nhớ và cần rất ít thời gian để tìm nạp nội dung của nó.

Nhưng trong trường hợp thứ hai, bạn đang tạo một đường ống, sau đó chuyển đầu ra của lệnh thứ nhất sang lệnh thứ hai bằng cách sử dụng đường ống này, việc này rất tốn kém.

Sự khác biệt là do /proc(vì nó nằm trong bộ nhớ) và đường ống, xem ví dụ dưới đây:

time for i in {1..1000};do grep ^MemFree /proc/meminfo;done >/dev/null

real    0m0.914s
user    0m0.032s
sys     0m0.148s


cat /proc/meminfo > file
time for i in {1..1000};do grep ^MemFree file;done >/dev/null

real    0m0.938s
user    0m0.032s
sys     0m0.152s


time for i in {1..1000};do echo "$a"|grep ^MemFree; done >/dev/null

real    0m1.016s
user    0m0.040s
sys     0m0.232s

1

Bạn đang gọi một lệnh bên ngoài trong cả hai trường hợp (grep). Cuộc gọi bên ngoài yêu cầu một mạng con. Ngã ba vỏ đó là nguyên nhân cơ bản cho sự chậm trễ. Cả hai trường hợp đều giống nhau, do đó: một độ trễ tương tự.

Nếu bạn chỉ muốn đọc tệp bên ngoài một lần và sử dụng nó (từ một biến) nhiều lần, đừng đi ra khỏi vỏ:

meminfo=$(< /dev/meminfo)    
time for i in {1..1000};do 
    [[ $meminfo =~ MemFree:\ *([0-9]*)\ *.B ]] 
    printf '%s\n' "${BASH_REMATCH[1]}"
done

Chỉ mất khoảng 0,1 giây thay vì toàn bộ 1 giây cho cuộc gọi grep.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.