Mam dość złożony program, który działa dziwnie podczas kompilacji z OpenMP w trybie debugowania MSVC 2010. Zrobiłem co w mojej mocy, aby stworzyć następujący minimalny przykład pracy (choć nie jest to naprawdę minimalny), który minicuje strukturę prawdziwego programu.OpenMP z MSVC 2010 Debug buduje dziwny błąd, gdy obiekt jest kopiowany
#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 ma vector
itp ale Element
, Base
, Evaluator
i Implementation
oddaje podstawową strukturę prawdziwego programu. Podczas kompilacji w trybie debugowania i uruchamiania debuggera asercja kończy się niepowodzeniem na Point (4)
.
Oto kilka szczegółów informacje debugowania, przeglądając stosy połączeń,
Przy wejściu Point (1)
lokalna i
ma wartość 371152
, co jest w porządku. Zmienna elem
nie pojawia się w ramce, co jest trochę dziwne. Ale ponieważ twierdzenie o Point (1)
nie zawodzi, myślę, że jest w porządku.
Potem wydarzyły się zwariowane rzeczy. Wywołanie eval
przez Evaluator
rozwiązuje swoją klasę podstawową, a więc Point (2)
zostało exectute. W tym momencie, debugers pokazuje, że elem
ma i_ = 499999
, który nie jest już w i
użyte do wygenerowania elem
w Evaluator
przed przekazaniem go wartością do Base::eval
jest. Następnym punktem jest Point (3)
, tym razem elem
ma i_ = 501682
, który jest poza zakresem i jest to wartość, gdy połączenie jest kierowane do Point (4)
i nie powiodło się potwierdzenie.
Wygląda na to, że gdy obiekt zostanie przekazany przez wartość, wartość jego członków zostanie zmieniona. Ponownie uruchom program wiele razy, podobne zachowania zachodzą, ale nie zawsze są powtarzalne. W prawdziwym programie, ta klasa jest zaprojektowana tak, aby lubić iterator, który iteruje po zbiorze cząsteczek. Chociaż to, co iteruje, nie jest exaclty jak pojemnik. W każdym razie chodzi o to, że jest wystarczająco mały, aby skutecznie przejść przez wartość. Dlatego też kod klienta wie, że ma swoją własną kopię Element
zamiast jakiegoś odniesienia lub wskaźnika, i nie musi martwić się o wątek (dużo), dopóki trzyma się z interfejsem Element
, który zapewnia tylko zapisz dostęp do pojedynczej pozycji całej kolekcji.
Próbowałem tego samego programu z GCC i Intel ICPC. Nie dzieje się nic nieoczekiwanego. I w prawdziwym programie, poprawne wyniki tam, gdzie zostały wyprodukowane.
Czy nie używałem OpenMP w niewłaściwy sposób? Myślałem, że elem
utworzony na około Point (1)
będzie lokalny dla ciała pętli. Ponadto w całym programie nie powstała wartość większa niż N
, skąd więc pochodzi ta nowa wartość?
Edit
spojrzałem dokładniej do debuggera, to pokazuje, że podczas gdy elem.i_
została zmieniona podczas elem
został przekazany przez wartość, wskaźnik elem.src_
nie zmienia się z nim.Ma taką samą wartość (adresu pamięci) po przekazywane przez wartość
Edit: Compiler flagi
użyłem CMake do generowania rozwiązanie MSVC. Muszę przyznać, że nie mam pojęcia, jak korzystać z MSVC lub systemu Windows w ogóle. 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ść jakiekolwiek problemy.
CUpewnij wygenerowany projekt, używając Visual Studio 10 Win64
cel, flagi kompilatora wydaje się być /DWIN32 /D_WINDOWS /W3 /Zm1000 /EHsc /GR /D_DEBUG /MDd /Zi /Ob0 /Od /RTC1
A oto wiersz polecenia znalezione w 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
Czy jest coś suspecious tutaj?
Czasami dziwne rzeczy mogą się zdarzyć, gdy niektóre kod jest kompilowany jako Uwalniania i niektórych jest kompilowany jako debugowania. Czy OpenMP, którego używasz, jest skompilowany z tymi samymi flagami/debugowaniem, co twój program? –
Nie jestem pewien co do tego pytania. Zwykle nie używam msvc z wyjątkiem testów. Jednak powyższy kod był pojedynczym programem plików. Więc domyślam się, że jakakolwiek flaga jest używana, jest używana dla całego programu. Czy istnieje specjalna opcja dla trybu debugowania openmp? Użyłem cmake, aby znaleźć flagę openmp, która zmienia się w put/be/openmp. @SethCarnegie –
czy kompilujesz OpenMP z tym plikiem, czy używasz biblioteki, która została skompilowana w innym czasie? –