CPU 是免費的,但 bash 腳本並未利用所有 CPU 資源
我執行了一個簡單的腳本來生成一個包含 6 個欄位的大型(10000000 行)csv 文件,其中一些欄位在每行/行中都發生了變化,使用while循環。這台機器的所有 (32) 個 CPU 都是空閒的,大量的 RAM (~31 Gb) 也是空閒的。
我用命令為腳本計時
/usr/bin/time -v bash script.01.sh
執行大約 2 小時後,我得到了以下統計數據:
正在計時的命令:“bash script.01.sh”
使用者時間(秒):1195.14
系統時間(秒):819.71
此作業獲得的 CPU 百分比:27%
已用(掛鐘)時間(h:mm:ss 或 m: ss): 2:01:10
平均共享文本大小 (kbytes): 0
平均非共享數據大小 (kbytes): 0
平均堆棧大小 (kbytes): 0
平均總大小 (kbytes): 0
最大駐留集大小 (kbytes): 4976
平均駐留集大小(千字節):0
主要(需要 I/O)頁面錯誤:0
次要(回收幀)頁面錯誤:3131983488
自願上下文切換:22593141
非自願上下文切換:10923348
交換:0
文件系統輸入:0
文件系統輸出:2182920
發送的套接字消息:0
接收的套接字消息:0
傳遞的信號:0
頁面大小(字節):4096
退出狀態:0
我想知道為什麼我的腳本只使用了 27% 的 CPU?磁碟 IO 根本不算什麼(在 vmstat 輸出中看到)。那麼是什麼造成了限制呢?腳本中的程式碼?
這是腳本:
#!/usr/bin/env bash number=1 while [[ $number -lt 10000001 ]] ; do fname="FirstName LastName $" lname="" email="fname.lname.$number@domain.com" password="1234567890" altemail="lname.fname.$number@domain.com" mobile="9876543210" echo "$fname,$lname,$email,$password,$altemail,$mobile" >> /opt/list.csv number=$(expr $number + 1) done
通過使用
strace
,我看到了這條線number=$(expr $number + 1)
導致 fork、路徑搜尋和
expr
. (我在 Ubuntu 上使用 bash 4.2.45)。文件系統、磁碟和程序成本導致 bash 僅獲得大約 28% 的 CPU。當我將該行更改為僅使用 shell 內置操作時
((number = number + 1))
bash 使用了大約 98% 的 CPU,腳本在半小時內執行。這是在單 CPU 1.5GHz Celeron 上。
原樣的腳本不執行任何並行執行的操作,因此擁有 32 個空閒 CPU 並沒有多大幫助。但是,您當然可以將其並行化,例如,將其拆分為 10 個並行執行的 100 萬次迭代循環,寫入 10 個不同的文件,然後使用
cat
組合它們。@Arthur2e5 添加了以下範常式序:
max=1000000 step=40000 tmp="$(mktemp -d)" # Spawning. For loops make a bit more sense in a init-test-incr pattern. for ((l = 0; l < max; l += step)); do ( for ((n = l + 1, end = (step + l > max ? max : step + l); n <= end; n++)); do # Putting all those things into the `buf` line gives you a 1.8x speedup. fname="FirstName LastName \$" lname="" email="fname.lname.$n@domain.com" password="1234567890" altemail="lname.fname.$n@domain.com" mobile="9876543210" buf+="$fname,$lname,$email,$password,$altemail,$mobile"$'\n' done printf '%s\n' "$buf" > "$tmp/$l" ) & done # spawning.. wait # Merging. The filename order from globbing will be a mess, # since we didn't format $l to some 0-prefixed numbers. # Let's just do the loop again. for ((l = 0; l < max; l += step)); do printf '%s\n' "$(<"$tmp/$l")" >> /opt/list.csv done # merging.. rm -rf -- "$tmp" # cleanup