J'ai un programme assez complexe qui fonctionne dans un comportement étrange lors de la construction avec OpenMP en mode débogage MSVC 2010. J'ai fait de mon mieux pour construire l'exemple de travail minimal suivant (bien que ce ne soit pas vraiment minimal), ce qui minimise la structure du vrai programme.OpenMP avec MSVC 2010 Debug construit bug étrange lorsque l'objet est copié
#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;
}
Le programme réel ne pas vector
etc. Mais le Element
, Base
, Evaluator
et Implementation
capture la structure de base du programme réel. Lorsque vous générez en mode débogage et exécutez le débogueur, l'assertion échoue à Point (4)
.
Voici quelques détails des informations de débogage, en consultant les piles d'appels,
A l'entrée Point (1)
, la i
locale a une valeur 371152
, ce qui est bien. La variable elem
n'apparaît pas dans le cadre, ce qui est un peu étrange. Mais puisque l'affirmation à Point (1)
ne manque pas, je suppose que c'est bien.
Ensuite, des choses folles sont arrivées. L'appel à eval
par Evaluator
résout à sa classe de base, et donc Point (2)
était exectuted. À ce stade, les debugers montre que le elem
a i_ = 499999
, qui n'est plus le i
utilisé pour créer elem
dans Evaluator
avant de le passer en valeur-Base::eval
. Le point suivant, il résout à Point (3)
, cette fois, elem
a i_ = 501682
, qui est hors de portée, et c'est la valeur lorsque l'appel est dirigé à Point (4)
et a échoué l'assertion.
Il semble que chaque fois que l'objet Element
est passé par valeur, la valeur de ses membres est modifiée. Réexécutez le programme plusieurs fois, des comportements similaires se produisent mais pas toujours reproductibles. Dans le programme réel, cette classe est conçue pour aimer un itérateur, qui parcourt une collection de particules. Bien que la chose itérer n'est pas exaclty comme un conteneur. Mais de toute façon, le fait est qu'il est suffisamment petit pour être efficacement passé en valeur. Et par conséquent, le code client, sait qu'il a sa propre copie de Element
au lieu d'une référence ou un pointeur, et n'a pas besoin de se soucier de thread-safe (beaucoup) tant qu'il reste avec l'interface de Element
, qui fournissent seulement écrire l'accès à une seule position de la collection entière.
J'ai essayé le même programme avec GCC et Intel ICPC. Rien d'inattendu ne se produit. Et dans le vrai programme, corrigez les résultats là où ils sont produits. Ai-je mal utilisé OpenMP quelque part? Je pensais que le elem
créé à environ Point (1)
doit être local au corps de la boucle. De plus, dans l'ensemble du programme, aucune valeur supérieure à N
n'a été produite, alors d'où vient cette nouvelle valeur?
Modifier
je plus attentivement regardé dans le débogueur, il montre que le pointeur elem.src_
ne change pas pendant elem.i_
a été changé quand elem
a été adoptée par la valeur, avec elle.Il a la même valeur (de l'adresse de la mémoire) après passé par valeur
Edit: drapeaux du compilateur
je CMake pour générer la solution MSVC. Je dois avouer que je n'ai aucune idée de comment utiliser MSVC ou Windows en général. La seule raison pour laquelle je l'utilise est que je sais que beaucoup de gens l'utilisent donc je veux tester ma bibliothèque pour éviter tout problème.
Le CMake généré projet, en utilisant Visual Studio 10 Win64
cible, les drapeaux du compilateur semble être /DWIN32 /D_WINDOWS /W3 /Zm1000 /EHsc /GR /D_DEBUG /MDd /Zi /Ob0 /Od /RTC1
Et voici la ligne de commande qui se trouve dans la propriété Pages-C/C++ - ligne de commande /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
Y at-il suspecious ici?
Parfois, des choses étranges peuvent se produire lorsqu'un code est compilé en tant que Release et que d'autres sont compilés en tant que Debug. L'OpenMP que vous utilisez est-il compilé avec les mêmes options de drapeaux/déboguage que votre programme? –
Je ne suis pas sûr de la question. Je n'utilise généralement pas msvc sauf pour les tests. Cependant, le code ci-dessus était un programme de fichiers unique. Donc je suppose que quel que soit le drapeau utilisé, il est utilisé pour l'ensemble du programme. Y at-il une option spéciale pour le mode de débogage openmp? J'ai utilisé cmake pour trouver le drapeau openmp, qui devient put/openmp. @SethCarnegie –
compilez-vous OpenMP avec ce fichier ou utilisez-vous une bibliothèque qui a été compilée à un autre moment? –