我有一個相當複雜的程序,在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