0

最悪のシナリオに達したときに比較的高価な計算を行うプログラムを書いています。私はスレッドを動的に作成しようとしましたが、これはほとんどの場合に機能することが証明されていますが、最悪のシナリオが発生した場合、実行速度はこれらの計算を完了するために割り当てられた時間を超えています。これらのスレッドの破壊。これは、スレッドを動的に作成して破棄するのではなく、実行前にスレッドを作成し、動的に作成する代わりに計算を実行する前にスレッドを待機させるという、過去に使用したアイデアに私を導きました。

通常、私はこれを行うことについて二度考えることはありませんが、システムの初期化中に大量のスレッドを作成することになるため、これがシステムのパフォーマンスにどのように影響するかが心配です. これはある疑問を投げかけました: ある条件で待機しているスレッドは、システムにどのような影響を与えるのでしょうか? プログラムの初期化中にスレッドを作成し、計算を実行する必要があるときにのみ通知することは、この問題にアプローチする正しい方法ですか、それとも私が知らないより良い解決策が存在しますか? これを行うためにスレッドプールを使用することも考えました。この状況にはスレッドプールが最適でしょうか?

この質問への回答を改善するために役立つと思われる情報:

-- プログラムをマルチスレッド化するために、boost ライブラリ (バージョン 1_54_0) を使用しています。

-- Windows 7 と Visual Studio を使用しています。

-- プログラムの初期化時にスレッドを作成すると、200 ~ 1000 のスレッドが作成されます (この数は #define として事前に決定されており、計算を行う必要があるたびにすべてのスレッドを使用するとは限りません)。

-- 必要なスレッドの数は、この計算を実行するたびに異なります。これは、計算が実行されるたびに変化する受信入力の数に依存しますが、最大値を超えることはありません (最大数はコンパイル時に #define として決定されます)。

--私が使用しているコンピューターには 32 コアがあります。

この質問が標準に達していない場合は申し訳ありません。私は新しいスタック オーバーフロー ユーザーなので、状況と問題をより適切に説明するにはどうすればよいか、さらに情報を求めて批評してください。よろしくお願いします。

アップデート

ソース コードは次のとおりです (一部の変数は、会社の利用規約に従って名前が変更されています)。

for(int i = curBlob.boundingBoxStartY; i < curBlob.boundingBoxStartY + curBlob.boundingBoxHeight; ++i)
{
    for(int j = curBlob.boundingBoxStartX; j < curBlob.boundingBoxStartX + curBlob.boundingBoxWidth; ++j)
    {
        for(int k = 0; k < NUM_FILTERS; ++k)
        {
            if((int)arrayOfBinaryValues[channel][k].at<uchar>(i,j) == 1)
            {
                for(int p = 0; p < NUM_FILTERS; ++p)
                {
                    if(p != k)
                    {
                        if((curBlob.boundingBoxStartX + 1 < (curBlob.boundingBoxStartX + curBlob.boundingBoxWidth)) && ((int)arrayOfBinaryValues[channel][k].at<uchar>(i + 1,j) == 1))
                            ++count;

                        if((curBlob.boundingBoxStartY + 1 < (curBlob.boundingBoxStartY + curBlob.boundingBoxHeight)) && ((int)arrayOfBinaryValues[channel][k].at<uchar>(i,j + 1) == 1))
                            ++count;
                    }
                }
            }
        }
    }
}

提供されるソース コードは、厳密にアルゴリズムの複雑さを示すためのものです。

4

1 に答える 1

9

スレッドが本当に待機している場合、スレッドはリソースをまったく消費しません。ほんの少しのメモリと、スケジューラの待機リストの「スペース」のスロットがいくつかあるだけです (したがって、処理するデータがもう少しあるため、スレッドを「ウェイク」または「待機」しますが、これらのキューは通常かなり効率的であるため、実際のスレッドが意味のある作業を行うアプリケーションでそれを測定できるとは思えません)。

もちろん、1 秒に 1 回であっても定期的にウェイクアップする場合、1 秒に 1 回ウェイクアップする 1000 スレッドは、ミリ秒ごとに 1 つのコンテキスト スイッチを意味し、パフォーマンスに影響を与える可能性があります。

ただし、ほとんどの場合、多くのスレッドを作成することは間違った解決策だと思います。スレッド内のロジックが複雑で、各スレッドで追跡する大量の状態/コンテキストがあり、この状態またはコンテキストをどこかに保存するのが容易でない場合を除き、これを行うのが正しい場合があります。しかし、ほとんどの場合、少数のワーカー スレッドを使用してから、作業項目のキュー (それぞれの状態またはコンテキストへの [何らかのタイプの参照を含む]) を使用する方が、これを実現するためのより良い方法になると思います。

問題の編集に基づいて編集:

(私が知る限り) スレッドは CPU (またはメモリ帯域幅) によって完全にバインドされているため、I/O やその他の「待機」がないため、システムのコアごとに 1 つのスレッドを実行することで最大のパフォーマンスが達成されます。 (おそらく、「ネットワーク経由の通信、ディスク I/O、実行する必要がある一般的な OS/システム作業など、実行する必要があるその他の作業の「マイナス 1」)。

コアの数よりも多くのスレッドを使用すると、CPU 上のコアよりも多くのスレッドを実行する準備ができている場合、処理が遅くなる可能性さえあります。 OS 側で余分なスレッド スケジューリング作業が発生し、その上、1 つのスレッドが実行されると、キャッシュに有用なコンテンツが読み込まれます。別のスレッドが同じ CPU コアで実行されるようになると、キャッシュは強制的に他のデータをキャッシュにロードし、「古い」スレッドが再び実行されるようになると、同じ CPU であってもリロードする必要があります。使用していたデータ。

私は簡単な実験を行い、私のプロジェクトの 1 つの数値を返します...

だから、「変な数字」を計算する小さなプロジェクトがあります。ここでは、「1 つのスレッドと複数のスレッドを実行するのにかかる時間の比較」として使用します。ここで各スレッドが使用するメモリはわずか数百バイトなので、キャッシュはおそらくまったく影響を与えません。したがって、ここでの唯一の変数は、「起動コスト」と、スレッド間の競合による限界オーバーヘッドです。スレッドの数は、オプションによって決まり-tます。それ-eは「何番で止まるか」です。

$ time ./weird -t 1 -e 50000 > /dev/null

real    0m6.393s
user    0m6.359s
sys 0m0.003s
$ time ./weird -t 2 -e 50000 > /dev/null

real    0m3.210s
user    0m6.376s
sys 0m0.013s
$ time ./weird -t 4 -e 50000 > /dev/null

real    0m1.643s
user    0m6.397s
sys 0m0.024s
$ time ./weird -t 8 -e 50000 > /dev/null

real    0m1.641s
user    0m6.397s
sys 0m0.028s
$ time ./weird -t 16 -e 50000 > /dev/null

real    0m1.644s
user    0m6.385s
sys 0m0.047s
$ time ./weird -t 256 -e 50000 > /dev/null

real    0m1.790s
user    0m6.420s
sys 0m0.342s
$ time ./weird -t 512 -e 50000 > /dev/null

real    0m1.779s
user    0m6.439s
sys 0m0.502s

ご覧のとおり、プロジェクト全体を「実行」する時間が 1 スレッドから 2 スレッドに、2 スレッドから 4 スレッドに改善されています。しかし、4 つ以上のスレッドを実行すると、数百に達するまでほぼ同じ結果が得られます (スレッド数を 2 倍にするためにいくつかの手順をスキップしました)。

ここで、スケジューリングのオーバーヘッドを示すために、「検索する数値」の数を増やして、その後-eに大きな数値を追加しました (数値が大きいほど計算が複雑になるため、プロセスの実行時間が長くなります)。

$ time ./weird -t 512 -e 100000 > /dev/null

real    0m7.100s
user    0m26.195s
sys 0m1.542s
$ time ./weird -t 4 -e 100000 > /dev/null

real    0m6.663s
user    0m26.143s
sys 0m0.049s

ここで、コストがかかるのが起動時間のみである場合、sys50000 になる 512 スレッドと 100000 になる 512 スレッドの間に同様のオーバーヘッドが見られるはずですが、3 倍の数値が見られます。したがって、6 ~ 7 秒のうち、512 スレッドを (フルスピードで) 実行すると、4 スレッドを実行すると、ほぼ 1.5 秒の CPU 時間が浪費されます (つまり、CPU あたり約 0.4 秒)。確かに、それは約 5% にすぎませんが、無駄な労力の 5% は依然として無駄になっています。アルゴリズムの 5% の改善が「価値がある」場合はたくさんあります。

はい、これは極端なケースであり、ほとんどのスレッドが待機している限り、それは問題ではないと主張できます。

于 2013-09-05T16:54:56.813 に答える