我有一个相当复杂的程序,在MSVC 2010调试模式下使用OpenMP进行生成时会出现奇怪的行为。我尽我所能来构建下面的最小工作示例(尽管它并不是最小的),它将真正的程序的结构缩小了。使用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
等。但Element
,Base
,Evaluator
和Implementation
捕捉真正的程序的基本结构。当以调试模式构建并运行调试器时,断言在Point (4)
处失败。
这里是调试信息的一些细节,通过查看调用栈,
在进入Point (1)
,当地i
具有价值371152
,这是罚款。变量elem
没有出现在框架中,这有点奇怪。但由于Point (1)
的断言并不失败,我想这很好。
然后,发生了疯狂的事情。 eval
Evaluator
的呼叫解析为其基类,因此Point (2)
被执行。此时,debugers显示elem
具有i_ = 499999
,它不再是i
,用于在Evaluator
中创建elem
,然后通过值,值为至Base::eval
。下一点,它解析为Point (3)
,这一次,elem
具有i_ = 501682
,这是超出范围的,这是当调用指向Point (4)
并且断言失败时的值。
它看起来像只要Element
对象通过值传递,它的成员的值被改变。重新运行该程序多次,类似的行为发生虽然不总是可重复的。在真正的程序中,这个类被设计成像迭代器一样,迭代器遍历一系列粒子。尽管它迭代的东西并不像容器那样脆弱。但无论如何,重要的是它足够小,可以有效地通过价值传递。因此,客户端代码知道它拥有自己的Element
副本,而不是一些引用或指针,并且只要他坚持使用Element
的接口,就不必担心线程安全(很多),它只提供将访问权限写入整个集合的单个位置。
我尝试了与GCC和Intel ICPC相同的程序。没有发生不可预料的事情。在真正的程序中,产生正确的结果。
我在某处错误地使用了OpenMP吗?我认为在Point (1)
创建的elem
应该是循环体的本地。另外,在整个计划中,没有产生大于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
有什么suspecious这里?
当某些代码编译为Release并且某些编译为Debug时,有时会出现奇怪的事情。您正在使用的OpenMP是否与您的程序使用相同的标志/调试内容编译? – 2012-07-13 23:04:56
我不确定这个问题。除测试外,我通常不使用msvc。但是上面的代码是一个单一的文件程序。所以我猜无论使用哪个标志,它都用于整个程序。调试模式openmp有特殊选项吗?我用cmake来查找openmp标志,结果是/ openmp。 @SethCarnegie – 2012-07-13 23:15:27
您是使用该文件编译OpenMP还是使用另一次编译的库? – 2012-07-13 23:16:09