13

元の問題:

そこで、スレッドを使って実験し、いくつかのテストを行うコードをいくつか書きました。

コードはいくつかの数値を作成し、それらの数値の平均を見つける必要があります。

私がこれまでに持っているものをお見せする方が簡単だと思います。コードが約 2 倍速く実行される 2 つのスレッドで期待していました。ストップウォッチで測ってみると 6倍ぐらい遅くなってる気がします!編集:現在、コンピューターと clock() 関数を使用して時間を伝えています。

void findmean(std::vector<double>*, std::size_t, std::size_t, double*);


int main(int argn, char** argv)
{

    // Program entry point
    std::cout << "Generating data..." << std::endl;

    // Create a vector containing many variables
    std::vector<double> data;
    for(uint32_t i = 1; i <= 1024 * 1024 * 128; i ++) data.push_back(i);

    // Calculate mean using 1 core
    double mean = 0;
    std::cout << "Calculating mean, 1 Thread..." << std::endl;
    findmean(&data, 0, data.size(), &mean);
    mean /= (double)data.size();

    // Print result
    std::cout << "  Mean=" << mean << std::endl;

    // Repeat, using two threads
    std::vector<std::thread> thread;
    std::vector<double> result;
    result.push_back(0.0);
    result.push_back(0.0);
    std::cout << "Calculating mean, 2 Threads..." << std::endl;

    // Run threads
    uint32_t halfsize = data.size() / 2;
    uint32_t A = 0;
    uint32_t B, C, D;
    // Split the data into two blocks
    if(data.size() % 2 == 0)
    {
        B = C = D = halfsize;
    }
    else if(data.size() % 2 == 1)
    {
        B = C = halfsize;
        D = hsz + 1;
    }

    // Run with two threads
    thread.push_back(std::thread(findmean, &data, A, B, &(result[0])));
    thread.push_back(std::thread(findmean, &data, C, D , &(result[1])));

    // Join threads
    thread[0].join();
    thread[1].join();

    // Calculate result
    mean = result[0] + result[1];
    mean /= (double)data.size();

    // Print result
    std::cout << "  Mean=" << mean << std::endl;

    // Return
    return EXIT_SUCCESS;
}


void findmean(std::vector<double>* datavec, std::size_t start, std::size_t length, double* result)
{
    for(uint32_t i = 0; i < length; i ++) {
        *result += (*datavec).at(start + i);
    }
}

このコードが素晴らしいとは思いません。改善方法を提案していただければ、それもありがたいです。

レジスタ変数:

関数「findmean」のローカル変数を作成することを提案する人が何人かいます。これは私がやったことです:

void findmean(std::vector<double>* datavec, std::size_t start, std::size_t length, double* result)
{
register double holding = *result;
for(uint32_t i = 0; i < length; i ++) {
    holding += (*datavec).at(start + i);
}
*result = holding;
}

私は今報告することができます: コードは、シングル スレッドとほぼ同じ実行時間で実行されます。これは 6 倍の大幅な改善ですが、2 倍近く速くする方法があるに違いありません。

変数と O2 の最適化を登録します。

最適化を「O2」に設定しました。結果を含むテーブルを作成します。

これまでの結果:

最適化またはレジスタ変数のない元のコード: 1 スレッド: 4.98 秒、2 スレッド: 29.59 秒

レジスター変数を追加したコード: 1 スレッド: 4.76 秒、2 スレッド: 4.76 秒

reg 変数と -O2 最適化の場合: 1 スレッド: 0.43 秒、2 スレッド: 0.6 秒2 スレッドは遅くなりましたか?

2 つの結果変数の間に大きなメモリ ブロックを配置するという Dameon の提案: 1 スレッド: 0.42 秒、2 スレッド: 0.64 秒

ベクトルの内容にアクセスするために反復子を使用する TAS の提案: 1 スレッド: 0.38 秒、2 スレッド: 0.56 秒

上記と同じ Core i7 920 (シングル チャネル メモリ 4GB): 1 スレッド: 0.31 秒、2 スレッド: 0.56 秒

上記と同じ Core i7 920 (デュアル チャネル メモリ 2x2GB): 1 スレッド: 0.31 秒、2 スレッド: 0.35 秒

4

4 に答える 4

19

2 スレッドが 1 スレッドよりも 6 倍遅いのはなぜですか?

誤った共有という悪いケースに見舞われています。

false-sharing を取り除いた後、2 スレッドが 1 スレッドよりも速くないのはなぜですか?

メモリ帯域幅がボトルネックになっています。


偽の共有:

ここでの問題は、各スレッドがresult隣接するメモリ位置で変数にアクセスしていることです。それらは同じキャッシュラインにある可能性が高いため、スレッドがそれにアクセスするたびに、コア間でキャッシュラインがバウンスされます。

各スレッドは次のループを実行しています。

for(uint32_t i = 0; i < length; i ++) {
    *result += (*datavec).at(start + i);
}

resultそして、変数が非常に頻繁に (反復ごとに) アクセスされていることがわかります。そのため、反復ごとに、スレッドは の両方の値を保持している同じキャッシュラインを求めて戦っていますresult

通常、コンパイラは*resultレジスタに入れる必要があり、それによってそのメモリ位置への一定のアクセスが削除されます。しかし、最適化をオンにしたことがないため、コンパイラが実際にメモリの場所にアクセスしている可能性が非常に高く、ループの反復ごとに偽共有ペナルティが発生します。

メモリ帯域幅:

誤った共有を排除し、6 倍のスローダウンを取り除いた後、改善が得られない理由は、メモリ帯域幅を使い果たしたためです。

確かにプロセッサは 4 コアかもしれませんが、それらはすべて同じメモリ帯域幅を共有しています。配列を合計するという特定のタスクは、各メモリアクセスに対してほとんど (計算) 作業を行いません。メモリ帯域幅を最大限に活用するには、単一のスレッドで十分です。したがって、より多くのスレッドに移動しても、多くの改善が得られる可能性は低くなります。

要するに、より多くのスレッドを配列に投入しても、配列の合計を大幅に高速化することはできません。

于 2013-06-27T16:21:50.287 に答える
0

これはおそらく、2 つのスレッドを起動して待機するコストが、1 つのループで結果を計算するよりもはるかに多いためです。データ サイズは 128MB です。これは、最新のプロセッサが単一のループで処理するにはあまり多くありません。

于 2013-06-27T16:21:31.393 に答える