web-dev-qa-db-ja.com

C ++ OpenMP Parallel Forループ-std :: vectorの代替

このスレッドに基づいて、 OpenMPおよびSTLベクトル であり、どのデータ構造がsharedstd :: vector in a並列forループ?主な側面は速度であり、ベクトルはループ中にサイズ変更が必要になる場合があります。

17

あなたがリンクした質問は、「そのSTLベクトルコンテナは、複数のスレッドが単一のコンテナに書き込む状況ではスレッドセーフではない」という事実について話していました。 _std::vector_が保持する基になる配列の再割り当てを引き起こす可能性のあるメソッドを呼び出す場合にのみ、これは正しく記述されています。 Push_back()pop_back()およびinsert()は、これらの危険なメソッドの例です。

スレッドセーフな再割り当てが必要な場合、ライブラリ intel thread building block が提供する concurrent vector container です。ランダム要素にアクセスするのにかかる時間は、std :: vectorが同じことをするのにかかる時間(O(1))よりも長いため、シングルスレッドプログラムではtbb :: concurrent_vectorを使用しないでください。ただし、同時ベクトルは、再割り当てが発生した場合でも、スレッドセーフな方法でPush_back()pop_back()insert()を呼び出します。

編集1: 次のIntelプレゼンテーション のスライド46と47は、tbb :: concurrent_vectorを使用した同時再割り当ての例を示しています

編集2:ちなみに、インテルトレッドビルディングブロックの使用を開始する場合(オープンソースであり、ほとんどのコンパイラーで動作し、openmpよりもC++/C++ 11機能との統合が優れています)、必要ありません。 openmpを使用してparallel_forを作成するには、 Here がtbbを使用したparallel_forの良い例です。

15
Vivian Miranda

ほとんどの場合、OpenMPでstd::vectorを使用できますが、パフォーマンスは良好です。たとえば、次のコードはstd::vectorsを並列で埋め、最後にそれらを結合します。メインのループ/フィル関数がボトルネックである限り、これは一般的にうまく機能し、スレッドセーフになるはずです。

std::vector<int> vec;
#pragma omp parallel
{
    std::vector<int> vec_private;
    #pragma omp for nowait //fill vec_private in parallel
    for(int i=0; i<100; i++) {
        vec_private.Push_back(i);
    }
    #pragma omp critical
    vec.insert(vec.end(), vec_private.begin(), vec_private.end());
}

編集:

OpenMP 4.0では、#pragma omp declare reductionを使用してユーザー定義の削減が可能です。上記のコードはtoで簡略化できます

#pragma omp declare reduction (merge : std::vector<int> : omp_out.insert(omp_out.end(), omp_in.begin(), omp_in.end()))

std::vector<int> vec;
#pragma omp parallel for reduction(merge: vec)
for(int i=0; i<100; i++) vec.Push_back(i);

編集:私がこれまでに示したものは、順番にベクトルを埋めません。順序が重要であれば、これはこのように行うことができます

std::vector<int> vec;
#pragma omp parallel
{
    std::vector<int> vec_private;
    #pragma omp for nowait schedule(static)
    for(int i=0; i<N; i++) { 
        vec_private.Push_back(i);
    }
    #pragma omp for schedule(static) ordered
    for(int i=0; i<omp_get_num_threads(); i++) {
        #pragma omp ordered
        vec.insert(vec.end(), vec_private.begin(), vec_private.end());
    }
}

これにより、各スレッドのstd :: vectorを保存してから、それらを並列領域外のシリアルにマージすることが回避されます。私はこの「トリック」について学びました ここ 。 ユーザー定義の削減に対してこれを行う方法(またはそれが可能かどうか)がわかりません。。ユーザー定義の削減でこれを行うことはできません。

この質問 parallel-cumulative-prefix-sums-in-openmp-communicating-values-between-thread からわかったクリティカルセクションは必要ないことに気づきました。このメソッドは、順序も正しく取得します

std::vector<int> vec;
size_t *prefix;
#pragma omp parallel
{
    int ithread  = omp_get_thread_num();
    int nthreads = omp_get_num_threads();
    #pragma omp single
    {
        prefix = new size_t[nthreads+1];
        prefix[0] = 0;
    }
    std::vector<int> vec_private;
    #pragma omp for schedule(static) nowait
    for(int i=0; i<100; i++) {
        vec_private.Push_back(i);
    }
    prefix[ithread+1] = vec_private.size();
    #pragma omp barrier
    #pragma omp single 
    {
        for(int i=1; i<(nthreads+1); i++) prefix[i] += prefix[i-1];
        vec.resize(vec.size() + prefix[nthreads]);
    }
    std::copy(vec_private.begin(), vec_private.end(), vec.begin() + prefix[ithread]);
}
delete[] prefix;
42
Z boson