yes
是如何快速寫入文件的?
讓我舉個例子吧:
$ timeout 1 yes "GNU" > file1 $ wc -l file1 11504640 file1
$ for ((sec0=`date +%S`;sec<=$(($sec0+5));sec=`date +%S`)); do echo "GNU" >> file2; done $ wc -l file2 1953 file2
在這裡您可以看到該命令在一秒鐘內
yes
寫入11504640
行,而我只能1953
在 5 秒內使用 bashfor
和echo
.正如評論中所建議的,有各種技巧可以提高效率,但沒有一個能接近匹配的速度
yes
:$ ( while :; do echo "GNU" >> file3; done) & pid=$! ; sleep 1 ; kill $pid [1] 3054 $ wc -l file3 19596 file3
$ timeout 1 bash -c 'while true; do echo "GNU" >> file4; done' $ wc -l file4 18912 file4
這些可以在一秒鐘內寫入多達 20,000 行。它們可以進一步改進為:
$ timeout 1 bash -c 'while true; do echo "GNU"; done >> file5' $ wc -l file5 34517 file5
$ ( while :; do echo "GNU"; done >> file6 ) & pid=$! ; sleep 1 ; kill $pid [1] 5690 $ wc -l file6 40961 file6
這些可以讓我們在一秒鐘內完成多達 40,000 行。
yes
更好,但與每秒可寫約 1100 萬行的情況相去甚遠!那麼,如何
yes
快速寫入文件呢?
簡而言之:
yes
表現出與大多數其他標準實用程序類似的行為,這些實用程序通常寫入FILE STREAM,輸出由 libC 通過stdio
. 這些僅write()
每隔 4kb (16kb 或 64kb)或任何輸出塊執行一次系統呼叫BUFSIZ
。echo
是一個write()
perGNU
。這是很多模式切換 (顯然,它不像context-switch那樣昂貴)。更不用說,除了它的初始優化循環之外,它
yes
是一個非常簡單、微小、編譯過的 C 循環,而且你的 shell 循環與編譯器優化的程序完全不同。但是我錯了:
當我之前說
yes
usedstdio
時,我只是假設它確實如此,因為它的行為與那些行為非常相似。這是不正確的——它只是以這種方式模仿他們的行為。它實際上所做的非常類似於我在下面對 shell 所做的事情:它首先循環以合併其參數*(或者y
如果沒有)*,直到它們可能不再增長而不超過BUFSIZ
.緊接相關循環之前來自源的評論指出:
for
/* Buffer data locally once, rather than having the large overhead of stdio buffering each item. */
yes``write()
此後做自己的。題外話:
(最初包含在問題中,並保留在此處已寫的可能資訊豐富的解釋的上下文中):
我試過
timeout 1 $(while true; do echo "GNU">>file2; done;)
但無法停止循環。您在命令替換方面遇到的
timeout
問題 - 我想我現在明白了,並且可以解釋為什麼它不會停止。timeout
不會啟動,因為它的命令行永遠不會執行。您的 shell 派生出一個子 shell,在其標準輸出上打開一個管道並讀取它。當孩子退出時它將停止閱讀,然後它將解釋所有孩子為$IFS
mangling 和 glob 擴展而編寫的內容,並根據結果替換從$(
匹配的所有內容)
。但是,如果孩子是一個永遠不會寫入管道的無限循環,那麼孩子永遠不會停止循環,並且
timeout
’ 的命令行永遠不會在之前完成*(我猜)*你這樣做Ctrl
+C
並殺死孩子循環。所以timeout
永遠 不能殺死需要在它開始之前完成的循環。其他
timeout
:… 與您的性能問題的相關性與您的 shell 程序必須花費在使用者模式和核心模式之間切換以處理輸出的時間量無關。
timeout
但是,它不像 shell 那樣靈活:shell 擅長處理參數和管理其他程序的能力。正如其他地方所指出的那樣,簡單地將*
[fd-num] >> named_file
*重定向移動到循環的輸出目標,而不是僅僅將輸出定向到循環的命令可以顯著提高性能,因為這樣至少open()
系統呼叫只需要執行一次。這也是在下面使用|
管道作為內部循環的輸出來完成的。直接比較:
你可能會喜歡:
for cmd in exec\ yes 'while echo y; do :; done' do set +m sh -c '{ sleep 1; kill "$$"; }&'"$cmd" | wc -l set -m done
256659456 505401
這有點像之前描述的命令子關係,但是沒有管道,並且子程序是後台的,直到它殺死父程序。在這種
yes
情況下,自從子程序生成後,父程序實際上已經被替換,但是 shellyes
通過用新程序覆蓋自己的程序來呼叫,因此 PID 保持不變,它的殭屍子程序仍然知道要殺死誰。更大的緩衝區:
現在讓我們看看增加 shell 的
write()
緩衝區。IFS=" "; set y "" ### sets up the macro expansion until [ "${512+1}" ] ### gather at least 512 args do set "$@$@";done ### exponentially expands "$@" printf %s "$*"| wc -c ### 1 write of 512 concatenated "y\n"'s
1024
我選擇了這個數字,因為長度超過 1kb 的輸出字元串對我來說會被分成單獨
write()
的 ‘s。所以這裡又是循環:for cmd in 'exec yes' \ 'until [ "${512+:}" ]; do set "$@$@"; done while printf %s "$*"; do :; done' do set +m sh -c $'IFS="\n"; { sleep 1; kill "$$"; }&'"$cmd" shyes y ""| wc -l set -m done
268627968 15850496
這是本次測試在相同時間內寫入的數據量的 300 倍,是上次測試的 300 倍。不是太寒酸。但事實並非如此
yes
。感覺:
根據要求,在此連結上對此處所做的操作進行了比僅程式碼註釋更全面的描述。