Bash

CPU 是免費的,但 bash 腳本並未利用所有 CPU 資源

  • October 15, 2015

我執行了一個簡單的腳本來生成一個包含 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

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