OpenMP com MSVC 2010 Debug criar bug estranho quando o objeto é copiado
Eu tenho um programa bastante complexo que é executado em comportamento estranho quando compilar com o OpenMP no modo de depuração do MSVC 2010. Eu tentei o meu melhor para construir o seguinte exemplo de trabalho mínimo (embora não seja realmente mínimo) que minic a estrutura do programa real.
#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;
}
O programa real não temvector
etc. Mas oElement
, Base
, Evaluator
eImplementation
captura a estrutura básica do programa real. Quando construir no modo de depuração e executando o depurador, a declaração falha emPoint (4)
.
Aqui estão mais alguns detalhes das informações de depuração, visualizando as pilhas de chamadas,
Ao entrarPoint (1)
, o locali
tem valor371152
que está bem. A variávelelem
não aparece no quadro, o que é um pouco estranho. Mas desde a afirmação emPoint (1)
não é faile, eu acho que está bem.
Então, coisas malucas aconteceram. A chamada paraeval
porEvaluator
resolve a sua classe base, e assimPoint (2)
foi extinto. Neste ponto, os depuradores mostram que oelem
temi_ = 499999
, que não é mais oi
usado para criarelem
emEvaluator
antes de passarpor valor paraBase::eval
. O próximo ponto, resolvePoint (3)
, desta vez,elem
temi_ = 501682
, que está fora do intervalo, e este é o valor quando a chamada é direcionada paraPoint (4)
e falhou a afirmação.
Parece sempreElement
objeto é passado por valor, o valor de seus membros é alterado. Execute novamente o programa várias vezes, comportamentos semelhantes acontecem, embora nem sempre reproduzíveis. No programa real, essa classe é projetada para gostar de um iterador, que itera uma coleção de partículas. Embora a coisa que itera não seja exatamente como um contêiner. Mas de qualquer forma, o ponto é que é pequeno o suficiente para ser passado eficientemente por valor. E, portanto, o código do cliente, sabe que tem sua própria cópia doElement
em vez de alguma referência ou ponteiro, e não precisa se preocupar com thread-safe (muito), enquanto ele fica comElement
's interface, que fornecem apenas acesso de gravação para uma única posição de toda a coleção.
Eu tentei o mesmo programa com o GCC e Intel ICPC. Nada não esperado acontece. E no programa real, os resultados corretos são produzidos.
Eu usei o OpenMP de forma errada em algum lugar? Eu pensei que oelem
criado em cerca dePoint (1)
deve ser local ao corpo do laço. Além disso, em todo o programa, nenhum valor maior queN
foi produzido, então de onde vem esse novo valor?
Editar
Eu olhei com mais cuidado para o depurador, isso mostra que, enquantoelem.i_
foi alterado quandoelem
foi passado por valor, o ponteiroelem.src_
não muda com isso. Tem o mesmo valor (do endereço de memória) depois de passado por valor
Editar: sinalizadores de compilador
Eu usei o CMake para gerar a solução MSVC. Eu tenho que confessar que não tenho idéia de como usar o MSVC ou o Windows em geral. A única razão pela qual estou usando é que eu sei que muitas pessoas o usam, então eu quero testar minha biblioteca contra ele para solucionar qualquer problema.
O projeto gerado pelo CMake, usandoVisual Studio 10 Win64
alvo, os sinalizadores do compilador parecem estar/DWIN32 /D_WINDOWS /W3 /Zm1000 /EHsc /GR /D_DEBUG /MDd /Zi /Ob0 /Od /RTC1
E aqui está a linha de comando encontrada em 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
Há algo de suspeito aqui?