私が作成したスクリプトは、バックグラウンドで別のプロセスを実行し、実行中のプロセスの最大数 (この場合は 300) を制御しようとするものです。
最初は約 1 ~ 2 ミリ秒でスクリプトを実行しますが、数時間実行すると、最終的には直線的に速度が低下し、200 ~ 350 ミリ秒で実行されます。配列を使用して PID 番号を維持していますが、キーを設定解除してテーブル サイズを縮小していますが、これが原因であるような気がします。
#!/bin/bash
threads=()
threadcounter=0
crd=0;
while true;
do
threadcounter=${#threads[@]}
crdcounter=${#crds[@]}
if [ "$threadcounter" -lt 300 ]
then
s1=$(($(date +%s%N)/1000000))
pidf=$(/opt/remi/php/root/usr/bin/php cli.php initformula $crd >> /tmp/logger) &
pidfid=$!
s2=$(($(date +%s%N)/1000000))
echo "Init " $crd $(expr $s2 - $s1 ) "ms"
threads[$pidfid]+=$pidfid
else
for pid in "${!threads[@]}"; do
if [ ! -d "/proc/${pid}" ]
then
unset threads[$pid]
fi
done;
fi;
if [ "$crd" -gt 9999999 ]
then
echo "completed all";
exit;
fi;
crd=$(expr $crd + 1)
done;
答え1
元のコード。
開始時に、 のコピーを 300 個起動しますcli.php
。起動にかかる時間を測定するため、これには約 1200 プロセスかかります。
次に、変数をcrd
300 から 9999999 までループします。
シェルは、配列内に予備のスロットがあると判断した場合、4 つのプロセスを使用して
threads
新しいものを開始します。cli.php
そうしないと、約 300 個のプロセスをループして
カーネルに/proc
仮想ファイルシステムを作成させ、ディレクトリが存在するかどうかをテストすることになります。ディレクトリが見つからないと、エントリが配列 から削除され
ます。threads
と呼ばれる未使用の配列がありますcrds
。
最初の 300 の後、cli.php
プロセス テーブルに空きスロットがある場合は crd 変数の各ループで 1 つの新しいコピーが作成されますが、テーブルがいっぱいの場合は最大 300 が削除されるため、実行の最後には 300 から約 9,967,000 のcli.php
プロセスが開始されたことしかわかりません。この数は、マシンの速度、cli.php
実行にかかる時間、およびマシンの負荷によって決まります。6 桁もの規模で最適化しなければならないのは、大変なことです。
経験則では、最新のマシンでは 1 つのコアで 1 つのプロセスを開始するのに 1 ミリ秒かかるため、初期の起動速度は悪くありません。新しいプロセスを起動するための空きコアがなくなると、起動速度が大幅に低下することが予想されます。
改善点
これを高速化する 1 つの方法は、! kill -0 $pid
ではなくを使用することです[ ! -d "/proc/${pid}" ]
。kill -0
は何も終了しませんが、プロセスが存在しない場合はエラーを返します。kill
はシェルの組み込み関数 ( と同じ[
) ですが、カーネルが行う作業量は少なくなります。ほとんどの場合、配列に空きスロットがない場合に、これが最も効果的ですthreads
。
2 番目の改善点は、外部プログラムへの呼び出しをexpr
組み込み演算に置き換えて$(( ... ))
、 のコピーを起動するオーバーヘッドを削減することですcli.php
。これは、ほとんどの場合、labels
配列に空きスロットがある場合に最も効果的です。
さらに詳細な分析を行うには、実行にかかるおおよその時間cli.php
と実行回数を知る必要があります。
BUGS
bash マニュアルのセクションにもあるようにIt's too big and too slow.
、bash の配列実装を改善する余地があることは確かです。
代替実装
作る
コメントではxargs
または を使うことが提案されていますparallel
。私は を使うことが多いですmake
。まず のコピーがいくつ必要かを決定します。次に、次のようなcli.php
簡単なMakefile
%:
\t/opt/remi/php/root/usr/bin/php cli.php initformula $@
ここで、\tはタブ文字です。(この単純なバージョンでは、0から9999999の範囲の数字の名前を持つファイルはないと仮定しています)。次に、次のようにmakeを起動します。
make -O -j 300 $(seq 0 9999999) > /tmp/logger
10,000,000 回の cli.php 呼び出しをすべて実行したい場合。cli.phpがエラーを返した場合に、処理を中止するための過度な手順を実行する必要がないようmake
にするためです。xargs
xargs
xargs
解決策としては
seq 0 9999999 | xargs -n 1 -P 300 /opt/remi/php/root/usr/bin/php cli.php initformula > /tmp/logger
それはより簡単です。
バッシュ
しかし、PID の追跡をまったく気にせず使用する Bash ソリューションの方が、wait -nf
OP の好みに合うかもしれません。最初の 300 プロセスを開始し、そのうちの 1 つが終了したことを検出すると、別のプロセスを起動します。10,000,000 番目のプロセスが開始されると、すべてのジョブが終了するまで最終的に待機します。まったく同じアルゴリズムではありませんが、非常に似ています。
#!/bin/bash
for(crd=0;crd<300;crd++); do
/opt/remi/php/root/usr/bin/php cli.php initformula $crd &
done > /tmp/logger
for(;crd<=9999999;crd++); do
wait -fn
/opt/remi/php/root/usr/bin/php cli.php initformula $crd &
done >> /tmp/logger
wait