3

次のシリアル関数を考えてみましょう。コードを並列化すると、すべてのスレッドが並列領域内からこの関数を呼び出します (表示されていません)。私はこれをスレッドセーフ効率的 (高速) にしようとしています。

float get_stored_value__or__calculate_if_does_not_yet_exist( int A )
{    
    static std::map<int, float> my_map;

    std::map::iterator it_find = my_map.find(A);  //many threads do this often.

    bool found_A =   it_find != my_map.end();

    if (found_A)
    {
        return it_find->second;
    } 
    else
    {
      float result_for_A = calculate_value(A);  //should only be done once, really.
      my_map[A] = result_for_A;
      return result_for_A;
    }    
}

ほとんどの場合、この関数が呼び出されるたびに、スレッドは格納されている "A" の値 (それが何であれ) を正常に "検索" します。時々、「新しい A」が呼び出されると、値を計算して保存する必要があります。

では、どこに置くべき#pragma omp criticalですか?

簡単ですが、各スレッドがこれを常に実行し、多くの場合読み取り専用になるため、これらすべてを回避するのは非常に非効率的です。#pragma omp critical

critical「一方向」または「一方向」lockルーチンを実装する方法はありますか? つまり、反復子を含む上記の操作はmy_mapelseステートメントへの書き込み時にのみ「ロック」する必要があります。.findただし、複数のスレッドが呼び出しを同時に実行できる必要があります。

私が理にかなっていることを願っています。ありがとうございました。

4

3 に答える 3

2

Stack Overflowのこのリンクによると、に挿入してstd::mapもイテレータは無効になりません。同じことがend()イテレータにも当てはまります。 こちらがサポートリンクです。

残念ながら、クリティカルセクションを使用しない場合、挿入は複数回発生する可能性があります。また、calculate_valueルーチンは計算コストがかかる可能性があるため、このelse句が同じ値で2回操作されてから、2回A挿入されないように、ロックする必要があります。

この誤った複数挿入を複製できるサンプル関数を次に示します。

void testFunc(std::map<int,float> &theMap, int i)
{
    std::map<int,float>::iterator ite = theMap.find(i);

    if(ite == theMap.end())
    {
         theMap[i] = 3.14 * i * i;
     }
}

次に、次のように呼び出されます。

std::map<int,float> myMap;

int i;
#pragma omp parallel for
for(i=1;i<=100000;++i)
{
    testFunc(myMap,i % 100);
}

if(myMap.size() != 100)
{
    std::cout << "Problem!" << std::endl;
}

編集:初期バージョンのエラーを修正するために編集されました。

于 2012-05-09T18:56:10.597 に答える
1

OpenMPは、自動ループ並列化のためのコンパイラー「ツール」であり、スレッド通信や同期ライブラリーではありません。したがって、読み取り/書き込みミューテックスのような高度なミューテックスはありません。書き込みではロックを取得しますが、読み取りではロックを取得しません。

これが実装例です。

とにかく、クリスA.の答えは私のものよりも優れています:)

于 2012-05-09T19:01:40.637 に答える
1

@ChrisAの回答で問題が解決するかもしれませんが、将来の検索者が役に立つと思う場合に備えて、ここに私の回答を残します.

必要に応じて、#pragma omp criticalセクションにname. 次に、その名前を持つすべてのセクションが同じクリティカル セクションと見なされます。これがやりたいことなら、メソッドの小さな部分だけを簡単にクリティカルにすることができます。

#pragma omp critical map_protect
{
    std::map::iterator it_find = my_map.find(A);  //many threads do this often.

    bool found_A =   it_find != my_map.end();
}

...

#pragma omp critical map_protect
{
    float result_for_A = calculate_value(A);  //should only be done once, really.
    my_map[A] = result_for_A;
}

#pragma omp atomicand#pragma omp flushディレクティブも役立つ場合があります。

atomicメモリ位置 (ディレクティブの前にある式の左辺値) への書き込みが常にアトミックになります。

flushすべてのスレッドで使用できると予想されるメモリが、実際にはすべてのスレッドに書き込まれ、プロセッサ キャッシュに格納されず、あるべき場所で使用できないことを保証します。

于 2012-05-09T18:55:34.100 に答える