2

他の誰かが、非常に多くのサブプロセスをフォークする bash スクリプトを (TM) 書きました。最適化が必要です。しかし、問題が「どれほど悪いか」を測定する方法を探しています。

このスクリプトによって全体として/再帰的に分岐されたサブプロセスの数を示すカウントを取得するにはどうすればよいですか?

これは、既存の fork コードがどのように見えるかを単純化したバージョンです - 貧乏人の 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

$1スクリプトが 1000 行の入力を探すのに 20 秒かかります。このコードはあまりにも多くのサブプロセスをフォークしています。実際のコードでは、、、などprogA | progB | progCを使用して各行で動作する長いパイプ ( など) があります。grepcutawksed

これは、他にも多くの処理が行われているビジー状態のシステムです。そのため、スクリプトの実行中にシステム全体でフォークされたプロセスの数を数えることは、ある程度は役に立ちます。このスクリプトと子孫によって開始されたプロセス。そして、スクリプトを分析して自分でカウントすることもできると思いますが、スクリプトは長くてかなり複雑なので、可能であれば、デバッグ用にこのカウンターを装備したいと思います。

明確にするために:

  • $$任意の時点でのプロセスの数を探しているのではなく(たとえば、を介してps)、スクリプトの存続期間全体で実行されるプロセスの数を探しています。
  • また、この特定のサンプル スクリプトのより高速なバージョンを探しているわけでもありません (それは可能です)。30 以上のスクリプトのどれを最初に最適化して bash ビルトインを使用するかを決定する方法を探しています。
4

1 に答える 1

3

forkSIGCHLD シグナルを単純にトラップする ed プロセスを数えることができます。スクリプト ファイルを編集できる場合は、次の操作を実行できます。

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

したがってfork、変数にはフォークの数が含まれます。最後に、この値を印刷できます:

echo $fork FORKS

1000 行の入力ファイルの場合、次のように出力されます。

3000 FORKS

このコードは 2 つの理由で分岐します。それぞれexpr ...に 1 つと に 1 つ`echo ...|grep...`。したがって、読み取り while ループでforkは、行が読み取られるたびに s になります。処理中の while ループはfork2 回です (1 つは のため、expr ...もう 1 つは のためです`echo ...|grep ...`)。したがって、1000 行のファイルの場合、3000 回フォークします。

しかし、これは正確ではありません。これは、呼び出し元のシェルによって行われた単なるフォークです。`echo ...|grep...`を開始してこのコードを実行するため、さらにフォークがあります。しかし、その後も 2 回フォークされます。つまり、実際には 1秒ではなく 3 秒です。つまり、3000 ではなく 5000 フォークです。echogrepfork

フォークのフォーク(フォークの...)もカウントする必要がある場合(または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]}

=~は (grep のように) RE ではなく ERE を使用することに注意してください。 (またはgrep -E) のように動作します。

definedlineArrayは無意味ではなく (そうでなければ、読み取りループで一致をテストでき、lineArrayは必要ありません)、他の目的にも使用されると思います。その場合、少し短いバージョンをお勧めします。

readarray -t lineArray <infile 

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

最初の行は、ループなしで完全infile に読み取ります。lineArray2 行目は、配列を要素ごとに処理します。

対策

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

したがって、このバージョンでは no fork(またはclone) はまったく使用されません。このバージョンは、小さい (<100 KiB) ファイルにのみ使用することをお勧めします。他の場合には、 が純粋なソリューションを実行します。ただし、これはパフォーマンス テストで確認する必要があります。

で1000行の場合、次のようになりました。

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

real    0m0.001s
user    0m0.000s
sys     0m0.001s
于 2013-07-18T10:46:56.820 に答える