元のコード。

元のコード。

私が作成したスクリプトは、バックグラウンドで別のプロセスを実行し、実行中のプロセスの最大数 (この場合は 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 プロセスかかります。

次に、変数をcrd300 から 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と実行回数を知る必要があります。

BUGSbash マニュアルのセクションにもあるように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 -nfOP の好みに合うかもしれません。最初の 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

関連情報