MSVC 2010 デバッグ モードで OpenMP を使用してビルドすると、奇妙な動作が発生するかなり複雑なプログラムがあります。実際のプログラムの構造を模倣した次の最小限の作業例 (実際には最小限ではありません) を作成するために最善を尽くしました。
#include <vector>
#include <cassert>
// A class take points to the whole collection and a position Only allow access
// to the elements at that posiiton. It provide read-only access to query some
// information about the whole collection
class Element
{
public :
Element (int i, std::vector<double> *src) : i_(i), src_(src) {}
int i () const {return i_;}
int size () const {return src_->size();}
double src () const {return (*src_)[i_];}
double &src () {return (*src_)[i_];}
private :
const int i_;
std::vector<double> *const src_;
};
// A Base class for dispatch
template <typename Derived>
class Base
{
protected :
void eval (int dim, Element elem, double *res)
{
// Dispatch the call from Evaluation<Derived>
eval_dispatch(dim, elem, res, &Derived::eval); // Point (2)
}
private :
// Resolve to Derived non-static member eval(...)
template <typename D>
void eval_dispatch(int dim, Element elem, double *res,
void (D::*) (int, Element, double *))
{
#ifndef NDEBUG // Assert that this is a Derived object
assert((dynamic_cast<Derived *>(this)));
#endif
static_cast<Derived *>(this)->eval(dim, elem, res);
}
// Resolve to Derived static member eval(...)
void eval_dispatch(int dim, Element elem, double *res,
void (*) (int, Element, double *))
{
Derived::eval(dim, elem, res); // Point (3)
}
// Resolve to Base member eval(...), Derived has no this member but derived
// from Base
void eval_dispatch(int dim, Element elem, double *res,
void (Base::*) (int, Element, double *))
{
// Default behavior: do nothing
}
};
// A middle-man who provides the interface operator(), call Base::eval, and
// Base dispatch it to possible default behavior or Derived::eval
template <typename Derived>
class Evaluator : public Base<Derived>
{
public :
void operator() (int N , int dim, double *res)
{
std::vector<double> src(N);
for (int i = 0; i < N; ++i)
src[i] = i;
#pragma omp parallel for default(none) shared(N, dim, src, res)
for (int i = 0; i < N; ++i) {
assert(i < N);
double *r = res + i * dim;
Element elem(i, &src);
assert(elem.i() == i); // Point (1)
this->eval(dim, elem, r);
}
}
};
// Client code, who implements eval
class Implementation : public Evaluator<Implementation>
{
public :
static void eval (int dim, Element elem, double *r)
{
assert(elem.i() < elem.size()); // This is where the program fails Point (4)
for (int d = 0; d != dim; ++d)
r[d] = elem.src();
}
};
int main ()
{
const int N = 500000;
const int Dim = 2;
double *res = new double[N * Dim];
Implementation impl;
impl(N, Dim, res);
delete [] res;
return 0;
}
vector実際のプログラムにはetcはありませんが、 Element、Base、EvaluatorおよびImplementationは実際のプログラムの基本的な構造を捉えています。デバッグ モードでビルドし、デバッガーを実行すると、アサーションが で失敗しPoint (4)ます。
コール スタックを表示して、デバッグ情報の詳細を次に示します。
に入るPoint (1)と、ローカルiには値がありますが371152、これで問題ありません。変数elemはフレームに表示されません。これは少し奇妙です。でも at のアサーションPoint (1)は失敗しないので大丈夫だと思います。
その後、クレイジーなことが起こりました。evalbyへの呼び出しはEvaluatorその基本クラスに解決されるため、実行Point (2)されました。この時点で、デバッガーは has を示していelemます。これは、値によってに渡す前に作成するために使用されなくi_ = 499999なりました。次のポイントは に解決されますが、今回はがあり、これは範囲外です。これは、コールが に向けられ、アサーションに失敗したときの値です。ielemEvaluatorBase::evalPoint (3)elemi_ = 501682Point (4)
Elementオブジェクトが値渡しされるたびに、そのメンバーの値が変更されるように見えます。プログラムを複数回再実行すると、常に再現できるとは限りませんが、同様の動作が発生します。実際のプログラムでは、このクラスはイテレータのように設計されており、パーティクルのコレクションを反復処理します。それが反復するものはコンテナのように正確ではありませんが。とにかく、ポイントは、値によって効率的に渡されるのに十分小さいということです。したがって、クライアント コードは、何らかの参照またはポインターの代わりに の独自のコピーがあることを認識しており、書き込みアクセスのみを提供する のインターフェイスElementに固執する限り、スレッド セーフについて (あまり) 心配する必要はありません。Elementコレクション全体の単一の位置に。
GCC と Intel ICPC で同じプログラムを試しました。予期しないことは何も起こりません。実際のプログラムでは、正しい結果が生成されます。
どこかで OpenMP を間違って使用しましたか? elem作成された at aboutPoint (1)はループ本体に対してローカルになると思いました。また、プログラム全体では、N生成された以上の価値はありませんが、その新しい価値はどこから来るのでしょうか?
編集
デバッガーをより注意深く調べたところ、値で渡されelem.i_たときに変更されたのにelem、ポインターelem.src_が変更されていないことがわかりました。値渡し後、(メモリアドレスの) 同じ値を持つ
編集:コンパイラフラグ
CMake を使用して MSVC ソリューションを生成しました。正直に言うと、MSVC や Windows の一般的な使い方がわかりません。私がそれを使用している唯一の理由は、多くの人がそれを使用していることを知っているため、問題を回避するためにライブラリをテストしたいからです.
CMake で生成されたプロジェクトは、Visual Studio 10 Win64ターゲットを使用して、コンパイラ フラグが次のように表示されます
/DWIN32 /D_WINDOWS /W3 /Zm1000 /EHsc /GR /D_DEBUG /MDd /Zi /Ob0 /Od /RTC1。プロパティ ページ - C/C++ - コマンド ラインにあるコマンド ラインは次のとおりです。
/Zi /nologo /W3 /WX- /Od /Ob0 /D "WIN32" /D "_WINDOWS" /D "_DEBUG" /D "CMAKE_INTDIR=\"Debug\"" /D "_MBCS" /Gm- /EHsc /RTC1 /MDd /GS /fp:precise /Zc:wchar_t /Zc:forScope /GR /openmp /Fp"TestOMP.dir\Debug\TestOMP.pch" /Fa"Debug" /Fo"TestOMP.dir\Debug\" /Fd"C:/Users/Yan Zhou/Dropbox/Build/TestOMP/build/Debug/TestOMP.pdb" /Gd /TP /errorReport:queue
ここで疑わしいものはありますか?