2013-07-18 48 views
2

別人已經寫了(TM)一些bash腳本,它分叉了很多子進程。它需要優化。但我正在尋找一種衡量問題「有多糟」的方法。如何計算分叉(子 - ?)進程的數量

我可以/如何得到一個計數,說明有多少個子進程被這個腳本總括/遞歸分叉?

這是一個什麼樣的存在,分叉的代碼看起來像一個簡化版本 - 一個窮人的grep的:

#!/bin/bash 

file=/tmp/1000lines.txt 
match=$1 

let cnt=0 
while read line 
do 
    cnt=`expr $cnt + 1` 
    lineArray[$cnt]="${line}" 
done < $file 
totalLines=$cnt 

cnt=0 
while [ $cnt -lt $totalLines ] 
do 
    cnt=`expr $cnt + 1` 
    matches=`echo ${lineArray[$cnt]}|grep $match` 
    if [ "$matches" ] ; then 
     echo ${lineArray[$cnt]} 
    fi 
done 

它需要的腳本20秒尋找$1在1000線的投入。該代碼分叉了太多的子流程。在實際代碼中,使用grep,cut,awksed等在每條線上操作的管道較長(例如,progA | progB | progC)。

這是一個忙碌的系統,其中有很多其他的東西正在進行,因此在腳本運行期間統計整個系統上有多少進程會對我有些用處,但我會更喜歡此腳本和後代所啓動的進程數。我想我可以分析腳本並自己計算腳本,但腳本很長並且相當複雜,所以如果可能的話,我只想用這個計數器進行調試。

澄清:

  • 我沒有在任何特定時間(例如,通過ps$$下尋找的進程數量,但腳本的整個生命過程中運行的進程數。
  • 我也沒有尋找這個特定示例腳本的更快版本(我可以這樣做)。我正在尋找一種方法來確定首先優化使用bash內置插件的30多個腳本中的哪一個。

回答

3

您可以統計ed進程簡單地捕獲SIGCHLD信號。如果您可以編輯腳本文件,你可以這樣做:

set -o monitor # or set -m 
trap "((++fork))" CHLD 

所以fork變量將包含叉的數量。在最後您可以打印此值:

echo $fork FORKS 

對於1000線輸入文件,它會打印:

3000 FORKS 

此代碼分叉的原因有兩個。一個用於expr ...,另一個用於`echo ...|grep...`。所以在閱讀while循環時,每次讀取一行時都是如此;在處理while-loop時它是2次(因爲expr ...一個和​​一個)。因此,對於1000行文件,它會分叉3000次。

但這不是確切的!這只是通話外殼完成的分叉。有更多的叉子,因爲`echo ...|grep...`分叉開始運行此代碼。但它也分叉兩次後:一個用於echo,另一個用於grep。所以實際上它並不是一個。因此,它是5000 FORKS,而不是3000.

如果您需要計算叉的叉(叉的... ...),以及(或者你不能修改bash腳本或者您希望它從其他腳本做),更準確的解決方案可以是使用

strace -fo s.log ./x.sh 

它將打印線是這樣的:

30934 execve("./x.sh", ["./x.sh"], [/* 61 vars */]) = 0 

那麼你需要使用像這樣的(第一個數字是PID)的東西,算獨特的PID:

awk '{n[$1]}END{print length(n)}' s.log 

在這個腳本我有5001的情況下(+1是原來的PID腳本)。

評論

其實在這種情況下,所有fork S可避免:

而不是

cnt=`expr $cnt + 1` 

使用

((++cnt)) 

而不是

matches=`echo ${lineArray[$cnt]}|grep $match` 
if [ "$matches" ] ; then 
    echo ${lineArray[$cnt]} 
fi 

您可以使用內部的模式匹配:

[[ ${lineArray[cnt]} =~ $match ]] && echo ${lineArray[cnt]} 

記住=~使用ERE不重(如grep)。所以它將表現得像(或grep -E),而不是

我假定定義lineArray不是沒有意義的(否則在讀取循環的匹配可以被測試,並且不需要lineArray),它被用於其他目的,以及。在這種情況下,我可能會建議一點點較短的版本:

readarray -t lineArray <infile 

for line in "${lineArray[@]}";{ [[ $line} =~ $match ]] && echo $line; } 

第一行讀取完整infilelineArray沒有任何循環。第二行是逐個元素處理數組。

措施

1000線(上)原始腳本:

$ time ./test.sh 
3000 FORKS 

real 0m48.725s 
user 0m14.107s 
sys  0m30.659s 

修改版

FORKS 

real 0m0.075s 
user 0m0.031s 
sys  0m0.031s 

同上

3000 FORKS 

real 0m4.745s 
user 0m1.015s 
sys  0m4.396s 

FORKS 

real 0m0.028s 
user 0m0.022s 
sys  0m0.005s 

所以這個版本根本不使用fork(或clone)。我可能會建議僅將此版本用於小型(< 100 KiB)文件。在其他情況下,,,執行純粹的解決方案。但是這應該通過性能測試來檢查。

對於一千行上我得到了以下:

$ time grep Solaris infile # Solaris is not in the infile 

real 0m0.001s 
user 0m0.000s 
sys  0m0.001s 
+0

哇,'陷阱 「((++叉))」 CHLD'。真的在那裏學到了東西。謝謝,祝你好運。 – shellter

+0

@shellter:別忘了'set -m'!它不適用於它。 – TrueY

+0

謝謝,我已經看到'set -o monitor'的引用並使用了它幾次。 – shellter