Bash

為什麼打開文件比讀取可變內容更快?

  • February 23, 2019

bash腳本中,我需要/proc/文件中的各種值。到目前為止,我有幾十行直接像這樣對文件進行 grepping:

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

為了提高效率,我將文件內容保存在一個變數中並grepped:

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

而不是多次打開文件,這應該只打開一次並 grep 變數內容,我認為這會更快 - 但實際上它更慢:

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

dash和也是如此zsh。我懷疑/proc/文件的特殊狀態是一個原因,但是當我將內容複製/proc/meminfo到正常文件並使用時,結果是相同的:

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

使用 here 字元串來保存管道會稍微快一些,但仍然不如文件快:

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

為什麼打開文件比從變數中讀取相同內容更快?

在這裡,這不是關於打開文件讀取變數的內容,而是更多關於是否分叉額外的程序。

grep -oP '^MemFree: *\K[0-9]+' /proc/meminfo派生一個執行grep打開的程序/proc/meminfo(一個虛擬文件,在記憶體中,不涉及磁碟 I/O)讀取它並匹配正則表達式。

其中最昂貴的部分是分叉程序並載入 grep 實用程序及其庫依賴項,進行動態連結,打開語言環境數據庫,磁碟上的數十個文件(但可能記憶體在記憶體中)。

相比之下,關於讀取的部分/proc/meminfo微不足道,核心需要很少的時間來生成其中的資訊,並且grep需要很少的時間來讀取它。

如果你在上面執行strace -c,你會看到用於讀取的一個open()和一個系統呼叫與啟動其他所有操作相比是花生(不計算分叉)。read()``/proc/meminfo``grep``strace -c

在:

a=$(</proc/meminfo)

在大多數支持該$(<...)ksh 運算符的 shell 中,shell 只是打開文件並讀取其內容(並去除尾隨的換行符)。bash不同並且效率低得多,因為它分叉一個程序來進行讀取並通過管道將數據傳遞給父程序。但是在這裡,它已經完成了一次,所以沒關係。

在:

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

shell 需要生成兩個程序,它們同時執行,但通過管道相互互動。管道的創建、拆除以及從中寫入和讀取的成本很少。更大的成本是產生額外的程序。程序的調度也有一些影響。

您可能會發現使用 zsh<<<運算符會稍微快一些:

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

在 zsh 和 bash 中,這是通過將 的內容寫入$a臨時文件來完成的,這比產生額外程序的成本要低,但與直接獲取數據相比可能不會給您帶來任何收益/proc/meminfo。這仍然比/proc/meminfo在磁碟上複製的方法效率低,因為臨時文件的寫入是在每次迭代時完成的。

dash不支持 here-strings,但它的 heredocs 是使用不涉及產生額外程序的管道實現的。在:

grep '^MemFree' << EOF
$a
EOF

外殼創建一個管道,分叉一個程序。子程序grep以其標準輸入作為管道的讀取端執行,父程序將內容寫入管道的另一端。

但是,管道處理和程序同步仍然可能比直接獲取數據更昂貴/proc/meminfo

內容/proc/meminfo很短,製作時間也不長。如果你想節省一些 CPU 週期,你想刪除昂貴的部分:分叉程序和執行外部命令。

像:

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

避免bash其模式匹配非常低效。使用zsh -o extendedglob,您可以將其縮短為:

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

請注意,這^在許多 shell 中是特殊的(Bourne、fish、rc、es 和 zsh,至少帶有擴展的 glob 選項),我建議引用它。另請注意,echo不能用於輸出任意數據(因此我在printf上面使用)。

引用自:https://unix.stackexchange.com/questions/501828