15

私は一般的にプログラミングに慣れていないので、私の質問に答えるときはそのことを覚えておいてください.

私は、大きな 3D 配列 (10 億要素) を取り、さまざまな軸に沿って要素を合計して、データの各側の射影の 2D 配列を生成するプログラムを持っています。ここでの問題は、プログラムが読み取りと書き込みの両方で常に R​​AM から情報を取得しているため、RAM を非常に集中的に使用することです。

問題は、プログラムをマルチスレッド化するとパフォーマンスが向上するか、それとも RAM アクセスのボトルネックに陥るかということです。マルチスレッドとは、2 コアまたは 4 コアのマルチスレッドを意味するだけで、それ以上ではありません。

それが役に立てば、私の現在のコンピューター構成は 2.4ghz コア 2 クアッド、1033 fsb、667 mhz で 4 GB RAM です。

前もって感謝します、

-偽物

編集:

ここにいる人々は、私が最初に予想していたこの質問にはるかに興味を持っているようです。質問を拡大し、興味のある人のためにいくつかのコードを投稿します。

まず第一に、私がどこから来たのかを理解できるように、私の背景を少し説明します。私は機械工学の大学院生で、機械工学とはほとんど関係のないトピックをどうにかして選ぶことができました。私は約 5 年前に Java 入門 (強制) のコースを 1 つ受講しましたが、約 1 か月前に本格的に論文を書き始めるまで、プログラミングに触れたことはありませんでした。また、電子工学とコンピューター工学のコースも受講しました (これも強制ですが、理由はまだわかりません)。マイクロコントローラー (8 ビット)、その内部動作、およびそれらの ASM コーディングを扱いました。それ以外は、プログラミングについてほとんど何も知りません。

コードは次のとおりです。

int dim = 1000;
int steps = 7 //ranges from 1 to  255

for (int stage = 1; stage < steps; stage++)
for (int j = 0; j < dim; j++)
    for (int i = 0; i < dim; i++)
    {
        sum = 0;
        for (int k = 0; k < dim; k++)
            if (partMap[(((i * dim) + k) * dim) + j] >= stage)
                sum++;

        projection[(j*dim) + i] = sum;
    } 

コードのこのセクションは、z 軸のみで動作します。メイン データは、その構築方法により、奇妙なアドレッシング システムを持っていますが、それについて心配する必要はありません。立方体の他の側面の射影を行うための他のコードもありますが、それらは非常に異なることを行います。

4

19 に答える 19

32

複数のコアにまたがるマルチスレッド化により、軸全体の合計に必要な時間を短縮できますが、特別な注意が必要です。シングル スレッド コードにいくつかの変更を加えると、実際にはパフォーマンスが大幅に向上する可能性があります。

  1. 使用可能なコアの数に一致する数のスレッドのみが必要です。これは CPU を集中的に使用する操作であり、スレッドが I/O を待機する可能性はほとんどありません。

  2. 配列全体が RAM に収まらない場合、上記の仮定は成り立たない可能性があります。配列の一部がページインおよびページアウトされる場合、一部のスレッドはページング操作が完了するまで待機します。その場合、プログラムは、コアよりも多くのスレッドを持つことでメリットが得られる可能性があります。ただし、多すぎると、コンテキスト切り替えのコストが原因でパフォーマンスが低下します。スレッド数を試してみる必要があるかもしれません。一般的なルールは、準備完了スレッド間のコンテキスト スイッチの数を最小限に抑えることです。

  3. 配列全体が RAM に収まらない場合は、ページングを最小限に抑える必要があります。各スレッドがメモリにアクセスする順序は、実行中のすべてのスレッドのメモリ アクセス パターンと同様に重要です。可能な限り、次の領域に移動する前にアレイの一部を終了し、カバーされた領域には決して戻らないようにする必要があります。

  4. 各コアは、メモリの完全に別個の領域にアクセスする必要があることでメリットが得られます。ロックやバスの競合によるメモリ アクセスの遅延を回避したい。キューブの少なくとも 1 つのディメンションについては、単純なはずです。各スレッドにキューブの独自の部分を設定します。

  5. 各コアは、RAM からフェッチするのではなく、キャッシュからより多くのデータにアクセスすることにもメリットがあります。これは、行をスキップするのではなく、内側のループが近くの単語にアクセスするようにループを順序付けることを意味します。

  6. 最後に、配列内のデータ型に応じて、Intel/AMD プロセッサ (さまざまな世代の SSE) の SIMD 命令は、複数のセルを一度に合計することで、シングル コアのパフォーマンスを加速するのに役立ちます。VC++ にはサポートが組み込まれています。

  7. 作業に優先順位を付ける必要がある場合は、最初にディスク ページングを最小限に抑え、次にメモリ アクセスを最適化して CPU キャッシュを利用し、その後でマルチスレッドに対処することをお勧めします。

于 2009-07-09T21:46:02.210 に答える
12

コードを最適化する唯一の方法があります。それは、実行していることが遅いことを理解し、それを少なくすることです。「それを少なくする」という特別な場合は、代わりにもっと速い何かをすることです。

まず、投稿されたコードに基づいて私が行っていることは次のとおりです。

#include <fstream>
#include <sstream>
using std::ios_base;

template<typename Iterator, typename Value>
void iota(Iterator start, Iterator end, Value val) {
    while (start != end) {
        *(start++) = val++;
    }
}

int main() {

    const int dim = 1000;
    const int cubesize = dim*dim*dim;
    const int squaresize = dim*dim;
    const int steps = 7; //ranges from 1 to  255
    typedef unsigned char uchar;

    uchar *partMap = new uchar[cubesize];
    // dummy data. I timed this separately and it takes about
    // a second, so I won't worry about its effect on overall timings.
    iota(partMap, partMap + cubesize, uchar(7));
    uchar *projection = new uchar[squaresize];

    for (int stage = 1; stage < steps; stage++) {
        for (int j = 0; j < dim; j++) {
                for (int i = 0; i < dim; i++)
                {
                        int sum = 0;
                        for (int k = 0; k < dim; k++)
                            if (partMap[(((i * dim) + k) * dim) + j] >= stage)
                                sum++;

                        projection[(j*dim) + i] = sum;
                }
        }

        std::stringstream filename;
        filename << "results" << stage << ".bin";
        std::ofstream file(filename.str().c_str(), 
            ios_base::out | ios_base::binary | ios_base::trunc);
        file.write((char *)projection, squaresize);
    }

    delete[] projection;
    delete[] partMap;
}

(編集:「射影」はucharではなくintの配列である必要があることに気づきました。私の悪いことです。これはいくつかのタイミングに違いをもたらしますが、大きすぎないことを願っています。)

次に、にコピーresult*.binしたgold*.binので、次のように将来の変更を確認できます。

$ make big -B CPPFLAGS="-O3 -pedantic -Wall" && time ./big; for n in 1 2 3 4 5
6; do diff -q results$n.bin gold$n.bin; done
g++  -O3 -pedantic -Wall   big.cpp   -o big

real    1m41.978s
user    1m39.450s
sys     0m0.451s

さて、現時点では100秒です。

したがって、低速な10億アイテムのデータ配列を通過していると推測して、ステージごとに1回ではなく、1回だけ通過してみましょう。

    uchar *projections[steps];
    for (int stage = 1; stage < steps; stage++) {
         projections[stage] = new uchar[squaresize];
    }

    for (int j = 0; j < dim; j++) {
            for (int i = 0; i < dim; i++)
            {
                    int counts[256] = {0};
                    for (int k = 0; k < dim; k++)
                            counts[partMap[(((i * dim) + k) * dim) + j]]++;

                    int sum = 0;
                    for (int idx = 255; idx >= steps; --idx) {
                        sum += counts[idx];
                    }
                    for (int stage = steps-1; stage > 0; --stage) {
                        sum += counts[stage];
                        projections[stage][(j*dim) + i] = sum;
                    }
            }
    }

    for (int stage = 1; stage < steps; stage++) {
        std::stringstream filename;
        filename << "results" << stage << ".bin";
        std::ofstream file(filename.str().c_str(),
            ios_base::out | ios_base::binary | ios_base::trunc);
        file.write((char *)projections[stage], squaresize);
    }

    for (int stage = 1; stage < steps; stage++) delete[] projections[stage];
    delete[] partMap;

少し速いです:

$ make big -B CPPFLAGS="-O3 -pedantic -Wall" && time ./big; for n in 1 2 3 4 5
6; do diff -q results$n.bin gold$n.bin; done
g++  -O3 -pedantic -Wall   big.cpp   -o big

real    1m15.176s
user    1m13.772s
sys     0m0.841s

さて、stepsこの例では非常に小さいので、「counts」配列を使用して多くの不要な作業を行っています。プロファイリングすらしなくても、256まで2回(配列をクリアするために1回、合計するために1回)カウントすることは、1000までカウントする(列に沿って実行する)ことと比較して非常に重要だと思います。それでは、それを変更しましょう。

    for (int j = 0; j < dim; j++) {
            for (int i = 0; i < dim; i++)
            {
                    // steps+1, not steps. I got this wrong the first time,
                    // which at least proved that my diffs work as a check
                    // of the answer...
                    int counts[steps+1] = {0};
                    for (int k = 0; k < dim; k++) {
                        uchar val = partMap[(((i * dim) + k) * dim) + j];
                        if (val >= steps) 
                            counts[steps]++;
                        else counts[val]++;
                    }

                    int sum = counts[steps];
                    for (int stage = steps-1; stage > 0; --stage) {
                        sum += counts[stage];
                        projections[stage][(j*dim) + i] = sum;
                    }
            }
    }

現在、実際に必要な数のバケットのみを使用しています。

$ make big -B CPPFLAGS="-O3 -pedantic -Wall" && time ./big; for n in 1 2 3 4 5
6; do diff -q results$n.bin gold$n.bin; done
g++  -O3 -pedantic -Wall   big.cpp   -o big

real    0m27.643s
user    0m26.551s
sys     0m0.483s

フラ。コードは最初のバージョンのほぼ4倍高速であり、同じ結果を生成します。私が行ったのは、計算が行われる順序を変更することだけです。マルチスレッドやプリフェッチについてはまだ検討していません。そして、私は高度に技術的なループ最適化を試みたことがなく、コンパイラーに任せただけです。したがって、これはまともなスタートと見なすことができます。

ただし、iotaが実行される1秒よりも桁違いに長い時間がかかります。したがって、おそらくまだ大きなメリットがあります。主な違いの1つは、iotaが、あちこちを飛び回るのではなく、1d配列を順番に実行することです。最初の回答で述べたように、キューブでは常に順番を使用することを目指す必要があります。

それでは、iループとjループを切り替えて、1行の変更を行いましょう。

            for (int i = 0; i < dim; i++)
    for (int j = 0; j < dim; j++) {

これはまだ順番ではありませんが、一度に100万バイトのキューブスライスに焦点を合わせていることを意味します。最新のCPUには少なくとも4MBのキャッシュがあるため、運が良ければ、プログラム全体で1回だけキューブの特定の部分のメインメモリにアクセスできます。さらに優れたローカリティを使用すると、L1キャッシュに出入りするトラフィックを減らすこともできますが、メインメモリが最も低速です。

どのくらいの違いがありますか?

$ make big -B CPPFLAGS="-O3 -pedantic -Wall" && time ./big; for n in 1 2 3 4 5
6; do diff -q results$n.bin gold$n.bin; done
g++  -O3 -pedantic -Wall   big.cpp   -o big

real    0m8.221s
user    0m4.507s
sys     0m0.514s

悪くない。実際、この変更だけで、元のコードが100秒から20秒になります。したがって、これは5倍の原因であり、他のすべてのことは5倍の原因です(上記の「ユーザー」と「リアルタイム」の時間の違いは、私のウイルススキャナーが'user'は、プログラムがCPUを占有した時間であり、'real'には、I / Oの待機中、または別のプロセスの実行時間を与えるために中断された時間が含まれます。

もちろん、私のバケットソートは、各列の値で行っていることはすべて可換で連想的であるという事実に依存しています。大きな値はすべて同じように扱われるため、バケットの数を減らすことだけが機能しました。これはすべての操作に当てはまるとは限らないため、各操作の内部ループを順番に調べて、それをどのように処理するかを理解する必要があります。

そして、コードはもう少し複雑です。各ステージで「何とか」を実行してデータを実行する代わりに、データを1回実行するだけですべてのステージを同時に計算しています。最初の回答で推奨したように、1回のパスで行と列の計算を開始すると、これはさらに悪化します。読みやすくするために、コードを関数に分割し始める必要があるかもしれません。

最後に、私のパフォーマンスの向上の多くは、「ステップ」が小さいという事実の最適化からもたらされました。でsteps=100、私は得る:

$ make big -B CPPFLAGS="-O3 -pedantic -Wall" && time ./big; for n in 1 2 3 4 5
6; do diff -q results$n.bin gold$n.bin; done
g++  -O3 -pedantic -Wall   big.cpp   -o big

real    0m22.262s
user    0m10.108s
sys     0m1.029s

これはそれほど悪くはありません。Steps = 100の場合、元のコードはおそらく約1400秒かかりますが、それを証明するために実行するつもりはありません。しかし、「ステップ」への時間依存性を完全に取り除いたわけではなく、劣線形にしたことを覚えておく価値があります。

于 2009-07-11T12:59:43.240 に答える
5

あなたのコードはどのように機能しますか。このようになりますか?

for each row: add up the values
for each column: add up the values
for each stack: add up the values

もしそうなら、「参照の局所性」について読みたいと思うかもしれません。データの保存方法によっては、スタックを実行しているときに、各値に対してキャッシュ ライン全体を取得する必要があることに気付く場合があります。これは、値がメモリ内で互いに近くにないためです。実際、10 億の値を使用すると、ディスクからはるばるデータを取得できます。ストライド (値間の距離) が長いシーケンシャル アクセスは、キャッシュの最悪の使用方法です。プロファイリングを試してみてください。行の合計よりもスタックの合計に時間がかかっている場合は、ほぼ間違いなくこれが原因です。

メモリ バス (*) を飽和させている可能性があると思います。その場合、マルチスレッドは、core2 クアッドが異なるコアに対して異なるバスを使用する場合にのみ役立ちます。ただし、バス帯域幅を飽和させていない場合、マルチスレッド化しても、この方法では最高のパフォーマンスを得ることができません。1 つではなく、4 つのコアがすべての時間をキャッシュ ミスに費やすことになります。

メモリ キャッシュに縛られている場合、メモリの各ページ/行にアクセスする回数をできるだけ少なくすることを目標にする必要があります。そのため、データを 1 回実行して、各値を 3 つの異なる合計に追加するなどのことを試してみます。それが単一のコアでより高速に実行される場合は、ビジネスになります。次のステップでは、1000x1000x1000 の立方体を使用すると、外出先で合計 300 万のキューブを使用できます。これもキャッシュに収まらないため、読み取り時と同じように書き込み時にキャッシュ ミスの問題が発生することを心配する必要があります。

RAM 内の 1000 個の隣接する値の行に沿って実行すると、それらがすべて共有する行の合計に追加され、列とスタック (格納されない) の隣接する合計にも追加されることを確認する必要があります。そのため、スタックの「2 乗」と同様に、列の合計の「2 乗」を適切な方法で格納する必要があります。そうすれば、約 12k のメモリをキャッシュに取り込むだけで、10 億個の値のうち 1000 個を処理できます (1000 個の値に対して 4k、さらに 1000 列の合計に対して 4k、さらに 1000 個のスタック合計に対して 4k)。それに対して、一度に1つの合計に集中するよりも多くの店舗を行っています(したがって、レジスターにある可能性があります).

だから私は何も約束しませんが、マルチスレッドかどうかにかかわらず、メモリアクセスの順序を見る価値があると思います。比較的少量のメモリのみにアクセスしながら、より多くの CPU 作業を行うことができれば、シングル スレッド バージョンの速度が向上しますが、コアが限られたキャッシュ、メモリを共有するため、マルチスレッドに適した状態になります。バス、およびメイン RAM。

(*) エンベロープ計算の裏側: インターネットからのランダムでランダムなレビューで、私がこれまでに見つけた Core2 プロセッサの推定最大 FSB 帯域幅は、12GB/秒のエクストリームで、それぞれ 4x199MHz の 2 チャネルです)。キャッシュ ラインのサイズは 64 バイトで、これはストライドよりも小さくなっています。したがって、列またはスタックを悪い方法で合計して、値ごとに 64 バイトを取得しても、1 秒あたり 2 億の値を処理している場合にのみバスが飽和します。私はそれがこのような速さ(全体で10〜15秒)のようなものではないと推測しています。

したがって、私の最初の推測はおそらくかなり外れていました。コンパイラまたは CPU が非常に巧妙なプリフェッチを挿入していない限り、1 つのコアで 2 つのチャネルと 1 サイクルあたり 4 つの同時転送を使用することはできません。ちなみに4コアで2チャンネル4同時転送は無理でした。一連のリクエストの有効なバス帯域幅は、物理的な制限よりもはるかに低くなる可能性があります。この場合、4 つのコアが 4 つの異なるキャッシュ ラインを要求しているため、マルチスレッドによる改善が期待できます。 FSB やキャッシュ コントローラに問題を起こすことなく、同時にロードされます。しかし、やはりレイテンシーは致命的であるため、合計値ごとに 1 つ未満のキャッシュ ラインをロードできる場合は、はるかに優れた結果が得られます。

于 2009-07-09T21:47:33.343 に答える
4

CPU と RAM の速度を指定していないため、一般的にはわかりません。4 つのスレッドを並行して合計しても RAM が飽和してボトルネック (CPU ではなく) になるとは思えないため、改善される可能性は十分にあります。

于 2009-07-09T21:15:04.173 に答える
3

私の直感によると、ささやかな改善が見られるでしょう。ただし、最適化の結果を予測することは、エラーが発生しやすいことで有名です。

試してみて、結果をベンチマークしてください。

于 2009-07-09T21:16:36.480 に答える
2

プログラミングに慣れていない場合、これは非常に難しいかもしれませんが、高速化するための非常に強力な方法は、GPU の能力を使用することです。VRAM は通常の RAM よりもはるかに高速であるだけでなく、GPU はコードを 128 以上のコアで並行して実行することもできます。もちろん、この量のデータにはかなり大きな VRAM が必要です。

この可能性を調べることにした場合は、nVidia CUDA を調べる必要があります。自分で調べたことはありませんが、このような問題を対象としています。

于 2009-07-09T21:58:21.280 に答える
2

マルチスレッド化によってパフォーマンスが向上したとしても、最適化へのアプローチは間違っていると思います。マルチコアは、CPU メーカーがより高速な CPU 速度を市場に出せる速度で提供する唯一の方法であるため、大流行しています。必ずしもそれらが素晴らしいプログラミング ツールであるからではありません (まだ多くの成熟が必要です)。

何よりも、使用しているアルゴリズムを常に確認してください。あなたのプログラムは非常に RAM を集中的に使用しているとのことですが、キャッシュ ヒットを改善するにはどうすればよいでしょうか? 計算を線形に適用できるように配列をソートする方法はありますか? 使用しているプログラミング言語は何ですか?低レベルの言語で最適化するメリットはありますか? 動的計画法を使用して結果を保存する方法はありますか?

一般に、数学的にもコンパイラの最適化としても、より効率的なアルゴリズムにすべてのリソースを費やしてから、マルチコアについて心配してください。もちろん、あなたはすでにその段階にいるかもしれません。その場合、このコメントはあまり役に立ちません;p

于 2009-07-09T21:49:28.207 に答える
2

特定のアプリケーションについて答える必要がある質問はよく知られています。

まず、作業は並列化可能ですか? アムダールの法則は、マルチスレッドでどれだけ高速化できるかの上限を示します。

次に、マルチスレッド ソリューションでは多くのオーバーヘッドが発生しますか? あなたは、プログラムが「プログラムがRAMから読み取りと書き込みの両方で常に情報を取得しているため、RAMを集中的に使用している」と言います。そのため、読み取り/書き込みによって大きな調整オーバーヘッドが発生するかどうかを判断する必要があります. これは簡単ではありません。各 CPU はいつでもコンピューターの RAM 全体 (読み取りと書き込みの両方) にアクセスできますが、これを行うと (ロックがなくても) メモリ アクセスが遅くなる可能性があります。これは、さまざまな CPU が独自のキャッシュを保持し、キャッシュ内の内容を調整する必要があるためです。互いに (CPU 1 はキャッシュ内に値を持ち、CPU 2 は RAM 内のその値を更新し、CPU 2 は CPU 1 にそのキャッシュを無効にするように指示する必要があります)。また、ロックが必要な場合 (メモリの「読み取りと書き込み」の両方を行っているため、これはほぼ保証されます) は、可能な限り競合を回避する必要があります。

第三に、あなたは記憶に縛られていますか?「RAM集中型。」「メモリバウンド」と同じではありません。現在 CPU バウンドの場合は、マルチスレッドを使用すると速度が向上します。現在メモリに縛られている場合は、マルチスレッドによって速度が低下することさえあります (1 つのスレッドがメモリに対して速すぎる場合、複数のスレッドではどうなるでしょうか?)。

第四に、何か他の理由で遅いですか?newアルゴリズムで大量のメモリをing またはing している場合malloc、それだけでオーバーヘッドが発生している可能性があります。 そして、多くのプラットフォームでは、newとの両方mallocがマルチスレッドをうまく処理できません。したがって、 が悪いために現在遅い場合malloc、マルチスレッド プログラムはさらに遅くなりmallocます。

ただし、全体として、コードを見なくても、CPU バウンドであると予想され、マルチスレッド化により速度が向上すると予想されます。実際、アムダールの法則が示唆するのとほぼ同じです。ただし、OpenMP や Intel の Threading Building Blocks ライブラリ、またはある種のスレッド キューを調べて、それを行うことができます。

于 2009-07-09T21:50:39.610 に答える
2

マルチスレッド化する前に、コードに対してプロファイラーを実行する必要があります。おそらく、優れた (おそらく) 無料の C++ プロファイラーがどこにあるのかについては、別の質問です。

これは、計算時間のかなりの部分を占めているコードのビットを特定するのに役立ちます。いくつかのプロファイリングの後にあちこちで調整すると、パフォーマンスに大きな違いが生じることがあります。

于 2009-07-09T21:53:43.677 に答える
2

マルチスレッドは、計算を独立して同時に処理できるチャンクに分割できる場合にのみ、コードを高速化します。


編集

私が上記のように言ったのは (ほとんど自動応答です)、多くの開発者がマルチスレッド コードに多くの時間を費やしているのに、パフォーマンスがまったく向上しないのを目にするからです。もちろん、それらは同じ (またはさらに遅いパフォーマンス) になり、複数のスレッドを管理することの複雑さが増します。

はい、質問をもう一度読み、マルチスレッドの恩恵を受ける特定のケースを考慮に入れると表示されます。

RAM は非常に高速なので、非常に多くのスレッドがない限り、メモリ帯域幅を飽和させるのは非常に難しいと思います。

于 2009-07-09T21:17:11.143 に答える
2

これが大きな IF である場合、適切にコーディングされていれば、間違いなく速度が向上します。私の教授の 1 人がいつも指摘しているように、人々はしばしばアルゴリズムをスレッド化しようとしますが、最終的には遅くなります。これは多くの場合、非効率的な同期が原因です。したがって、基本的に、スレッド化を詳しく調べたい場合は (正直なところ、プログラミングに慣れていない場合はお勧めしません)、試してみてください。

あなたの特定のケースでは、同期は非常に簡単です。つまり、各スレッドを大規模な 3 次元マトリックスの象限に割り当てることができます。この場合、各スレッドは、入力マトリックスと出力マトリックスの特定の領域にのみアクセスできることが保証されています。 ' 複数のアクセス/書き込みからのデータ。

要約すると、この特定の単純なケースでは、スレッド化は非常に簡単かもしれませんが、一般的に、同期化が不十分な場合、プログラムの実行時間が長くなる可能性があります。それは本当にすべて依存します。

于 2009-07-09T21:19:03.387 に答える
1

マトリックスの問題ですか?

Intel と AMD の両方が、あらゆる種類の重い数学の問題に対応できるように最適化されたライブラリを持っています。これらのライブラリは、スレッド化、最適なキャッシュ使用のためのデータの配置、キャッシュ プリフェッチ、SSE ベクトル命令を使用します。すべての。

図書館は有料だと思いますが、それだけの価値はあります。

于 2009-07-11T20:43:57.883 に答える
1

偽の共有を排除する

これは、同じブロック キャッシュを共有する異なるメモリ アドレスの読み取りまたは更新を試みる複数のコアが互いにブロックしている場所です。プロセッサ キャッシュのロックはブロックごとに行われ、一度にそのブロックに書き込むことができるスレッドは 1 つだけです。

Herb Sutter には、False Sharing に関する非常に優れた記事があります。それを発見する方法と、並列アルゴリズムでそれを回避する方法です。

もちろん、彼は並行プログラミングに関する優れた記事を他にもたくさん持っています。彼のブログを参照してください。

于 2009-07-10T12:52:19.147 に答える
1

通常、コンピューター システムには、大まかなパフォーマンスを制限する要素がいくつかあります。どの部分があなたの制限要素であるかは、具体的な状況によって異なります。通常、次の要因のいずれかがパフォーマンスの問題の原因である可能性があります。

  • ディスク I/O 帯域幅: ほとんどのエンタープライズ アプリケーションでは、処理されるデータのサイズが非常に大きいため、データを何らかのデータベースに格納する必要があります。このデータへのアクセスは、最大転送速度の両方によって遅くなる可能性がありますが、多くの場合、最大の影響は、あちこちのブロックを読み取る多数の小さなディスク アクセスによって引き起こされます。ディスクのヘッドが動き回る待ち時間が表示され、ディスクが完全に回転するのに必要な時間でさえ、アプリケーションが制限される場合があります。かなり前に、大規模な SUN E430 インストールを使用して、小さな NeXTstation よりも優れたパフォーマンスを発揮する本当の問題がありました...書き込みアクセスをキャッシュしていないディスクによって速度が低下したのは、データベースの一定の fsync()ing でした (正当な理由で) . 通常、ディスクを追加して 1 秒あたりの I/O を増やすことで、システムを高速化できます。

  • ネットワーク遅延: アプリケーションの速度に影響を与えるほぼすべてのディスクについて言及されていることは、ネットワーク I/O についても同様です。

  • RAM: RAM が完全なアプリケーション イメージを格納するのに十分でない場合は、外部ディスクに格納する必要があります。したがって、ディスク I/O の速度低下が再びあなたを苦しめます。

  • CPU 処理速度 (整数または浮動小数点のいずれか): CPU 処理能力は、CPU 集中型タスクの制限となる次の要因です。CPU には、到達できない物理的な速度制限があります。速度を上げる唯一の方法は、CPU を追加することです。

これらの制限は、特定の問題に対する答えを見つけるのに役立つ場合があります。

単純に処理能力の向上が必要で、システムに複数の CPU またはコアが搭載されていますか? その場合、マルチスレッドによりパフォーマンスが向上します。

ネットワークまたはディスクの待ち時間がかなりありますか? これが表示された場合、貴重な CPU が遅い I/O を待って CPU サイクルを浪費している可能性があります。複数のスレッドがアクティブな場合、このスレッドは、処理に必要なすべてのデータをメモリ内で検出し、無駄な CPU サイクルを取得する可能性があります。

したがって、既存のアプリケーションを観察する必要があります。シャッフルされたデータのメモリ帯域幅を拡張しようとします。アプリケーションが 1 つの CPU で 100% 未満でアクティブになっている場合は、メモリ帯域幅の制限に達している可能性があります。その場合、追加のスレッド化は役に立ちません。メモリからの帯域幅が得られないためです。

CPU が 100% の場合は、試してみてください。ただし、アルゴリズムを確認してください。マルチスレッド化により、同期のための追加のオーバーヘッド (および複雑さ、複雑さ) が追加され、メモリ帯域幅がわずかに減少する可能性があります。きめの細かい同期を回避して実装できるアルゴリズムを優先します。

I/O 待機時間が表示される場合は、巧妙なパーティショニングまたはキャッシングについて考えてから、スレッド化について考えてください。90 年代に GNU-make が並列ビルドをサポートしたのには理由があります :-)

あなたが説明した問題のドメインは、最初に巧妙なアルゴリズムを見てみることに私を導きます。CPU とメモリ サブシステムを可能な限りサポートするために、可能な限りメイン メモリで順次読み取り/書き込み操作を使用するようにしてください。操作を「ローカル」に保ち、データ構造を可能な限り小さく最適化して、2 番目のコアに切り替える前にシャッフルする必要があるメモリの量を減らします。

于 2009-07-09T21:42:16.757 に答える
0

ビットを扱っているだけなら、ページングやスワップファイルを使用する必要はないかもしれません。その場合、YESマルチスレッドが役立ちます。

一度にすべてをメモリにロードできない場合は、ソリューションをより具体的にする必要があります。つまり、スレッド化に合わせて調整する必要があります。

例: 配列を小さなブロックにロードするとします (サイズはそれほど重要ではないかもしれません)。1000x1000x1000 の立方体をロードする場合、それを合計できます。結果は一時的に独自の 3 つのプレーンに保存され、次に 3 つの「最終結果」プレーンに追加されます。その後、1000^3 ブロックが破棄され、二度と読み取られることはありません。

このようなことを行うと、メモリが不足することはなく、スワップファイルにストレスがかかることもなく、いくつかの非常に小さな特定の領域を除いて、スレッドの同期について心配する必要もありません (存在する場合)。

唯一の問題は、データが 1 つの 1000^3 キューブに直接アクセスできるような形式であることを確認することです。ハード ディスクのヘッドをあちこち探し回ることはありません。

編集:コメントは正しかったし、私は間違っていた - 彼は完全に理にかなっている.

昨日から、問題全体を読み込めば解決できることに気付きました。読み込んだ各データは、すぐに結果に加算して破棄することができます。そのように考えると、スレッドが衝突することなく同時に 2 つのストリームを読み取れない限り、あまり役に立ちません。

于 2009-07-09T22:19:27.413 に答える
0

このコードを試してください:

int dim = 1000;
int steps = 7 //ranges from 1 to  255

for (int stage = 1; stage < steps; stage++)
for (int k = 0; k < dim; k++)
    for (int i = 0; i < dim; i++)
    {
            sum = 0;
            for (int j = 0; j < dim; j++)
                    if (partMap[(((i * dim) + k) * dim) + j] >= stage)
                            projection[i*dim + j] ++ ;
                            // changed order of i and j
    }


transponse(projection)

コードキャッシュを使いやすくするためにループの順序を変更しました...これにより、パフォーマンスが大幅に向上します...安心してください。

これは、マルチスレッドを実行する前に実行する必要がある手順です

于 2009-07-11T13:12:44.710 に答える
0

スレッドが配列内の同じ位置に読み書きしないように配列を分割できれば、速度が向上するはずです。

于 2009-07-09T21:18:33.217 に答える