為什麼打開文件比讀取可變內容更快?
在
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
上面使用)。