У меня довольно сложная программа, которая запускается в странное поведение при сборке с помощью OpenMP в режиме отладки MSVC 2010. Я попытался изо всех сил построить следующий минимальный рабочий пример (хотя он и не очень минимальный), который минимизирует структуру реальной программы.
#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
захватывает основную структуру реальной программы. Когда вы создаете режим Debug и запускаете отладчик, утверждение терпит неудачу при Point (4)
.
Ниже приведена подробная информация об отладочной информации, просмотрев стопки вызовов,
При входе Point (1)
локальный i
имеет значение 371152
, что отлично. Переменная elem
не отображается в кадре, что немного странно. Но так как утверждение в Point (1)
не исчезает, я думаю, это прекрасно.
Затем произошли сумасшедшие вещи. Вызов eval
на Evaluator
вызывает его базовый класс, и поэтому Point (2)
был эксплицирован. На этом этапе отладчики показывают, что elem
имеет i_ = 499999
, который больше не является i
, используемым для создания elem
в Evaluator
, прежде чем передать его по значению на Base::eval
. Следующий момент, он решает Point (3)
, на этот раз elem
имеет i_ = 501682
, который выходит за пределы диапазона, и это значение, когда вызов направлен на Point (4)
и не удалось выполнить утверждение.
Похоже, всякий раз, когда объект Element
передается по значению, значение его членов изменяется. Повторно запускайте программу несколько раз, подобное поведение происходит, хотя и не всегда воспроизводимое. В реальной программе этот класс предназначен как итератор, который перебирает коллекцию частиц. Хотя то, что он повторяет, не является чем-то вроде контейнера. Но в любом случае, дело в том, что оно достаточно мало, чтобы быть эффективно переданным по значению. И поэтому код клиента знает, что у него есть своя копия Element
вместо некоторой ссылки или указателя, и не нужно беспокоиться о потокобезопасности (много), если он придерживается интерфейса Element
, который только обеспечивают доступ на запись к одной позиции всей коллекции.
Я попробовал ту же программу с GCC и Intel ICPC. Ничего непредвиденного не происходит. И в реальной программе правильные результаты при их создании.
Я неправильно использовал OpenMP? Я думал, что созданный elem
около Point (1)
должен быть локальным для тела цикла. Кроме того, во всей программе не было создано значения больше N
, так где же происходит новое значение?
Edit
Я посмотрел более осторожно в отладчик, он показывает, что в то время как elem.i_
был изменен, когда elem
был передан по значению, указатель elem.src_
не изменится вместе с ним. Он имеет то же значение (адрес памяти) после передачи значением
Изменить: флаги компилятора
Я использовал CMake для создания решения MSVC. Должен признаться, я понятия не имею, как использовать MSVC или Windows в целом. Единственная причина, по которой я его использую, - это то, что я знаю, что многие люди используют его, поэтому я хочу протестировать свою библиотеку против него, чтобы решить любые проблемы.
Проект сгенерированный CMake с использованием Visual Studio 10 Win64
target, флаги компилятора
/DWIN32 /D_WINDOWS /W3 /Zm1000 /EHsc /GR /D_DEBUG /MDd /Zi /Ob0 /Od /RTC1
И вот командная строка, найденная в Property Pages-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
Есть ли что-то подозрительное здесь?