2012-05-09 14 views
3

次のシリアル関数を検討してください。私のコードを並列化すると、すべてのスレッドは並列領域内からこの関数を呼び出します(図示せず)。私はこのスレッドセーフ効率的(高速)にしようとしています。C++ OpenMP critical:「一方向」ロック?

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ルーチンを実装する方法はありますか?つまり、イテレータを含む上記の操作は、elseステートメントのmy_mapに書き込むときにのみ "ロック"されるべきです。しかし、複数のスレッドが.findコールを同時に実行できる必要があります。

私は意味があると思います。 ありがとうございます。

答えて

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 atomic#pragma omp flushディレクティブにも有用である可能性があります。

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

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

+0

:)しかし私のものよりも優れている。しかしcritical' 'の使用は、私が心配していた非効率である - 複数のスレッドがsimlutaneouslyによる' critical'にマップを読み取ることができません。 – cmo

+0

しかし 'atomic'は良いアイデアです。では、書き込み領域の周りに「アトミック」と「フラッシュ」を配置するだけでいいですか? - 読み込み領域は指示する必要がありますか? – cmo

+0

@CycoMatto私は知っています。私は「原子」と「フラッシュ」で賢明な何かをして、私の間違いを認識し、私の答えを編集したと思った。それはあなたの問題につまずく誰かに有用かもしれない情報を提供しますが、あなたが望むロックされた場合の書き込み動作を提供していません。 –

2

this link on Stack Overflowによると、std::mapに挿入してもイテレータは無効になりません。同じことがイテレータのend()にも当てはまります。 Here's a supporting link.

残念ながら、クリティカルセクションを使用しないと、挿入は複数回発生する可能性があります。また、あなたのcalculate_valueルーチンは計算上高価かもしれないので、Aの同じ値で2回操作してから2回挿入することを避けるために、このelse節をロックする必要があります。

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; 
} 

編集:earlerバージョンでエラーを修正するために編集した

は、ここでは、この間違った複数の挿入を複製することができますサンプル関数です。

+0

'.insert'が" mid-find "であっても、これは真ですか? (すなわち、このスレッドが '.find'呼び出しの中にある間)。 – cmo

+0

挿入操作は複数回呼び出すことができますが、*実際に書き込む操作は、*使用する挿入演算子によって* 1回か2回以上発生することがあります。 'operator [] 'を使用します。 'myMap [i] = 3.14 * i * i'は複数の書き込みになります。しかし、 'myMap.insert(std :: pair (i、3.14 * i * i))'は実際にはたった一つしか書きません。 – cmo

1

OpenMPはスレッド通信または同期ライブラリではなく、自動ループ並列化用のコンパイラ "ツール"です。したがって、読み書きのmutexのような洗練されたmutexはありません:書き込み時にロックを取得しますが、読み込み時には取得しません。

ここにはimplementation exampleがあります。

とにかくクリスA.の答えは