OpenMP mit MSVC 2010 Debug erzeugt einen seltsamen Fehler, wenn Objekte kopiert werden

Ich habe ein ziemlich komplexes Programm, das beim Erstellen mit OpenMP im MSVC 2010-Debug-Modus auf seltsames Verhalten stößt. Ich habe mein Bestes versucht, das folgende minimale Arbeitsbeispiel zu konstruieren (obwohl es nicht wirklich minimal ist), das die Struktur des realen Programms minimiert.

#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;
}

Das echte Programm hat das nichtvector etc. Aber dieElement, Base, Evaluator undImplementation erfasst die Grundstruktur des realen Programms. Wenn im Debug-Modus erstellt und der Debugger ausgeführt wird, schlägt die Assertion bei fehlPoint (4).

Hier finden Sie weitere Details zu den Debug-Informationen, indem Sie die Aufrufstapel anzeigen.

Beim BetretenPoint (1), die lokalei hat Wert371152, was in Ordnung ist. Die Variableelem wird im Frame nicht angezeigt, was etwas seltsam ist. Aber seit der Behauptung beiPoint (1) scheitert nicht, ich denke, es ist in Ordnung.

Dann passierten verrückte Dinge. Der Anruf nacheval durchEvaluator wird in seine Basisklasse aufgelöst, und soPoint (2) wurde ausgeführt. An dieser Stelle zeigt der Debugger, dass derelem hati_ = 499999, das ist nicht mehr diei verwendet, um zu erstellenelem imEvaluator bevor Sie es passierennach Wert zuBase::eval. Der nächste Punkt wird gelöstPoint (3), diesmal,elem hati_ = 501682Dies ist der Wert, an den der Anruf gerichtet istPoint (4) und scheiterte die Behauptung.

Es sieht aus wie wann immerElement Wird das Objekt als Wert übergeben, wird der Wert seiner Mitglieder geändert. Führen Sie das Programm mehrmals aus. Ein ähnliches Verhalten ist jedoch nicht immer reproduzierbar. Im realen Programm ist diese Klasse so konzipiert, dass sie einem Iterator ähnelt, der über eine Sammlung von Partikeln iteriert. Obwohl das Ding, das es durchläuft, nicht genau wie ein Container ist. Der Punkt ist jedoch, dass es klein genug ist, um effizient als Wert übergeben zu werden. Und deshalb weiß der Client-Code, dass er eine eigene Kopie von hatElement statt irgendeines Verweises oder Zeigers, und muss sich nicht um Thread-Safe (viel) kümmern, solange er dabei bleibtElementDie Benutzeroberfläche, die nur Schreibzugriff auf eine einzelne Position der gesamten Sammlung bietet.

Ich habe das gleiche Programm mit GCC und Intel ICPC ausprobiert. Es passiert nichts Unerwartetes. Und im realen Programm wurden korrekte Ergebnisse erzielt.

Habe ich OpenMP irgendwo falsch benutzt? Ich dachte, dass dieelem erstellt um ca.Point (1) muss lokal zum Schleifenkörper sein. Außerdem ist im gesamten Programm kein Wert größer alsN wurde produziert, also woher kommt der neue Wert?

Bearbeiten

Ich habe mir den Debugger genauer angesehenelem.i_ wurde geändert alselem Als Wert wurde der Zeiger übergebenelem.src_ ändert sich nicht damit. Es hat den gleichen Wert (der Speicheradresse), nachdem der Wert übergeben wurde

Bearbeiten: Compiler-Flags

Ich habe CMake verwendet, um die MSVC-Lösung zu generieren. Ich muss gestehen, dass ich keine Ahnung habe, wie man MSVC oder Windows im Allgemeinen verwendet. Der einzige Grund, warum ich es benutze, ist, dass ich weiß, dass viele Leute es benutzen, deshalb möchte ich meine Bibliothek damit testen, um Probleme zu umgehen.

Das mit CMake erzeugte ProjektVisual Studio 10 Win64 Ziel scheinen die Compiler-Flags zu sein/DWIN32 /D_WINDOWS /W3 /Zm1000 /EHsc /GR /D_DEBUG /MDd /Zi /Ob0 /Od /RTC1 Und hier ist die Befehlszeile in 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

Gibt es hier etwas Verdächtiges?

Antworten auf die Frage(1)

Ihre Antwort auf die Frage