我有一个相当复杂的程序,在 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)
没有失败,我想这很好。
然后,疯狂的事情发生了。eval
对by的调用Evaluator
解析为它的基类,因此Point (2)
被执行。此时,调试器显示elem
has ,在将其通过值传递给之前i_ = 499999
不再i
用于创建。下一点,这一次,它解析为has ,它超出了范围,这是调用被定向到并且断言失败时的值。elem
Evaluator
Base::eval
Point (3)
elem
i_ = 501682
Point (4)
看起来每当Element
对象按值传递时,其成员的值都会更改。多次重新运行程序,会发生类似的行为,但并不总是可以重现。在实际程序中,这个类被设计成一个迭代器,它迭代一个粒子集合。虽然它迭代的东西不像容器那样精确。但无论如何,关键是它足够小,可以有效地按值传递。因此,客户端代码知道它有自己的副本Element
而不是一些引用或指针,并且只要他坚持Element
只提供写访问的接口,就不需要担心线程安全(太多)到整个集合的单个位置。
我用 GCC 和 Intel ICPC 尝试了相同的程序。没有什么意外发生。在实际程序中,产生正确的结果。
我是否在某处错误地使用了 OpenMP?我认为elem
大约创建的Point (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
这里是在Property Pages-C/C++-Command Line中找到的命令行
/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
这里有什么可疑之处吗?