OpenMP z MSVC 2010 Debuguj dziwny błąd podczas kopiowania obiektu
Mam dość złożony program, który działa w dziwny sposób podczas budowania z OpenMP w trybie debugowania MSVC 2010. Starałem się jak najlepiej skonstruować następujący minimalny przykład pracy (choć nie jest to naprawdę minimalny), który minimalizuje strukturę prawdziwego programu.
#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;
}
Prawdziwy program nie mavector
itd. Ale toElement
, Base
, Evaluator
iImplementation
rejestruje podstawową strukturę prawdziwego programu. Po zbudowaniu w trybie debugowania i uruchomieniu debugera twierdzenie kończy się niepowodzeniemPoint (4)
.
Oto kilka szczegółowych informacji o debugowaniu, przeglądając stosy połączeń,
Przy wjeździePoint (1)
, lokalnyi
ma wartość371152
, co jest w porządku. Zmiennaelem
nie pojawia się w ramce, co jest trochę dziwne. Ale od czasu twierdzenia wPoint (1)
nie zawodzi, chyba jest w porządku.
Potem wydarzyły się szalone rzeczy. Wezwanie doeval
przezEvaluator
rozwiązuje do swojej klasy bazowej i tak dalejPoint (2)
został wyegzekwowany. W tym momencie debugery pokazują, żeelem
mai_ = 499999
, co nie jest jużi
używane do tworzeniaelem
wEvaluator
przed podaniemwedług wartości doBase::eval
. Następny punkt, to postanawiaPoint (3)
, tym razem,elem
mai_ = 501682
, który jest poza zakresem, i jest to wartość, do której kierowane jest połączeniePoint (4)
i nie spełnił tego twierdzenia.
Wygląda jak zawszeElement
obiekt jest przekazywany przez wartość, zmienia się wartość jego członków. Uruchom program wielokrotnie, podobne zachowania zdarzają się, choć nie zawsze powtarzalne. W prawdziwym programie ta klasa jest przeznaczona do iteratora, który iteruje po zbiorze cząstek. Chociaż to, co iteruje, nie jest tak bardzo jak pojemnik. Ale w każdym razie chodzi o to, że jest wystarczająco mały, aby można go było skutecznie przekazać według wartości. A zatem kod klienta wie, że ma własną kopięElement
zamiast jakiegoś odwołania lub wskaźnika, i nie musi się martwić o bezpieczne wątki (dużo), dopóki się trzymaElement
interfejs, który zapewnia dostęp do zapisu tylko do jednej pozycji całej kolekcji.
Próbowałem tego samego programu z GCC i Intel ICPC. Nie dzieje się nic nieoczekiwanego. A w prawdziwym programie poprawne wyniki tam, gdzie powstały.
Czy gdzieś użyłem OpenMP nieprawidłowo? Myślałem, żeelem
stworzony okołoPoint (1)
powinien być lokalny względem ciała pętli. Ponadto w całym programie nie ma wartości większej niżN
został wyprodukowany, więc skąd pochodzą te nowe wartości?
Edytować
Przyjrzałem się uważniej debugerowi, który to pokazujeelem.i_
został zmieniony kiedyelem
został przekazany przez wartość, wskaźnikelem.src_
nie zmienia się wraz z nim. Ma tę samą wartość (adresu pamięci) po przekazaniu przez wartość
Edytuj: Flagi kompilatora
Użyłem CMake do wygenerowania rozwiązania MSVC. Muszę przyznać, że nie mam pojęcia, jak używać MSVC lub ogólnie systemu Windows. Jedynym powodem, dla którego go używam, jest to, że wiem, że wiele osób go używa, więc chcę przetestować moją bibliotekę, aby obejść wszelkie problemy.
Projekt wygenerowany przez CMake przy użyciuVisual Studio 10 Win64
cel, flagi kompilatora wydają się być/DWIN32 /D_WINDOWS /W3 /Zm1000 /EHsc /GR /D_DEBUG /MDd /Zi /Ob0 /Od /RTC1
A oto linia poleceń znajdująca się na stronach właściwości - C / C ++ - Wiersz poleceń/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
Czy jest tu coś podejrzanego?