OpenMP: Probleme mit Ausgabe



  • Hallo COM,

    ich fange gerade mit OpenMP an und versuche mich natürlich als erstes an einem simplen HelloWorldAnsatz-Programm, nur halt jetzt mit OpenMP auf paralleler Art.

    Folgende Infos:

    OS: Scientific Linux 6.1 x64 (up2date)
    Kompiler: g++ (GCC) 4.4.5 20110214 (Red Hat 4.4.5-6)
    +kompatibel zu/ab: OpenMP v3.0

    Installiert via Yum:
    OpenMP - x86_64:
    libgomp.x86_64 : GCC OpenMP v3.0 shared support library
    Glibc - x86_x64 - v2.12-1.25.el6_1.3:
    glibc.x86_64 : The GNU libc libraries
    glibc-common.x86_64 : Common binaries and locale data for glibc
    glibc-devel.x86_64 : Object files for development using standard C libraries.
    glibc-headers.x86_64 : Header files for development using standard C libraries.

    Dateien zu OpenMP (GOMP) sind dabei hier zu finden:
    Libgomp (relevant):
    /usr/lib/gcc/x86_64-redhat-linux/4.4.4/include/omp.h
    /usr/lib64/libgomp.so.1.0.0
    /usr/lib64/libgomp.so.1
    /usr/lib/gcc/x86_64-redhat-linux/4.4.4/32/libgomp.a
    /usr/lib/gcc/x86_64-redhat-linux/4.4.4/32/libgomp.so
    /usr/lib/gcc/x86_64-redhat-linux/4.4.4/libgomp.spec
    /usr/lib/gcc/x86_64-redhat-linux/4.4.4/libgomp.a
    /usr/lib/gcc/x86_64-redhat-linux/4.4.4/libgomp.so

    Libgomp (all Results under /usr/):
    /usr/share/info/libgomp.info.gz
    /usr/share/doc/libgomp-4.4.5
    /usr/share/doc/libgomp-4.4.5/ChangeLog.bz2
    /usr/i686-pc-mingw32/sys-root/mingw/bin/libgomp-1.dll
    /usr/lib64/gcc/i686-pc-mingw32/4.4.2/libgomp.spec
    /usr/lib64/gcc/i686-pc-mingw32/4.4.2/libgomp.a
    /usr/lib64/gcc/i686-pc-mingw32/4.4.2/libgomp.dll.a
    /usr/lib64/libgomp.so.1.0.0
    /usr/lib64/R/library/stats/html/SSgompertz.html
    /usr/lib64/libgomp.so.1
    /usr/lib/gcc/x86_64-redhat-linux/4.4.4/32/libgomp.a
    /usr/lib/gcc/x86_64-redhat-linux/4.4.4/32/libgomp.so
    /usr/lib/gcc/x86_64-redhat-linux/4.4.4/libgomp.spec
    /usr/lib/gcc/x86_64-redhat-linux/4.4.4/libgomp.a
    /usr/lib/gcc/x86_64-redhat-linux/4.4.4/libgomp.so

    Arten der Kompilierungsaufrufe:

    Debug (short):
    g++ -std=c++0x -m64 -fopenmp -Wall -Wextra -pedantic -pedantic-errors -g -ggdb3 source.cpp -o dest

    Debug (long):
    g++ -std=c++0x -m64 -mmmx -msse -msse2 -msse3 -mssse3 -msse4 -msse4a -msse4.1 -msse4.2 -mavx -msse2avx -maes -mpclmul -mfma -mfma4 -mxop -mlwp -mabm -m3dnow -mmovbe -mpopcnt -mcld -mcmodel=large -mcrc32 -mcx16 -msahf -fopenmp -Wall -Wextra -pedantic -pedantic-errors -g -ggdb3 source.cpp -o dest
    **
    Release (short):**
    g++ -std=c++0x -m64 -fopenmp -Wall -Wextra -pedantic -pedantic-errors -O3 -s source.cpp -o dest

    Release (long):
    g++ -std=c++0x -m64 -mmmx -msse -msse2 -msse3 -mssse3 -msse4 -msse4a -msse4.1 -msse4.2 -mavx -msse2avx -maes -mpclmul -mfma -mfma4 -mxop -mlwp -mabm -m3dnow -mmovbe -mpopcnt -mcld -mcmodel=large -mcrc32 -mcx16 -msahf -fopenmp -Wall -Wextra -pedantic -pedantic-errors -O3 -s source.cpp -o dest

    **WICHTIG: Die nachfolgenden "Probleme" treten auch beim normalen Aufruf, ausschließlich mit -fopenmp und -o auf!!!!

    -------------------------------------------

    Ich habe vier vom Quellcode her minimalistisch verschiedene Beispiel-Quellcodes:

    helloworld1.cpp:

    #ifdef _OPENMP
    #include <omp.h>
    #endif
    
    #include <stdio.h>
    #include <iostream>
    
    using namespace std;
    
    int main(int argc, char* argv[])
    {
      #pragma omp parallel
      {
        printf("I am thread %d of %d.\n", omp_get_thread_num()+1, omp_get_num_threads());
      }
    
      getchar();
      return 0;
    }
    

    helloworld2.cpp:

    #ifdef _OPENMP
    #include <omp.h>
    #endif
    
    #include <stdio.h>
    #include <iostream>
    
    using namespace std;
    
    int main(int argc, char* argv[])
    {
      #pragma omp parallel
      {
        cout << "I am thread " << (omp_get_thread_num()+1) << " of " << omp_get_num_threads() << "\n";
      }
    
      getchar();
      return 0;
    }
    

    helloworld3.cpp:

    #ifdef _OPENMP
    #include <omp.h>
    #endif
    
    #include <stdio.h>
    #include <iostream>
    
    using namespace std;
    
    int main(int argc, char* argv[])
    {
      int NUMTHREADS, THREADID;
    
      #pragma omp parallel
      {
        THREADID = omp_get_thread_num();
        NUMTHREADS = omp_get_num_threads();
    
        cout << "I am thread " << THREADID << " of " << NUMTHREADS  << "\n";
      }
    
      getchar();
      return 0;
    }
    

    helloworld4.cpp:

    #ifdef _OPENMP
    #include <omp.h>
    #endif
    
    #include <stdio.h>
    #include <iostream>
    
    using namespace std;
    
    int main(int argc, char* argv[])
    {
      int NUMTHREADS, THREADID;
    
      #pragma omp parallel private(THREADID,NUMTHREADS)
      {
        THREADID = omp_get_thread_num();
        NUMTHREADS = omp_get_num_threads();
    
        cout << "I am thread " << THREADID << " of " << NUMTHREADS  << "\n";
      }
    
      getchar();
      return 0;
    }
    

    -------------------------------------------

    Das Problem dabei ist Folgendes:
    Die erste ursprüngliche CPP-Datei: "helloworld1.cpp", funktioniert anstandslos, soll heißen, kompiliert gibt sie alles schön der Reihe nach aus:

    Output - helloworld1:

    I am thread 22 of 24.
    I am thread 4 of 24.
    I am thread 5 of 24.
    I am thread 7 of 24.
    I am thread 15 of 24.
    I am thread 24 of 24.
    I am thread 9 of 24.
    I am thread 17 of 24.
    I am thread 19 of 24.
    I am thread 16 of 24.
    I am thread 11 of 24.
    I am thread 1 of 24.
    I am thread 23 of 24.
    I am thread 13 of 24.
    I am thread 14 of 24.
    I am thread 18 of 24.
    I am thread 2 of 24.
    I am thread 20 of 24.
    I am thread 21 of 24.
    I am thread 10 of 24.
    I am thread 8 of 24.
    I am thread 6 of 24.
    I am thread 3 of 24.
    I am thread 12 of 24.

    Das Programm ist jedoch sehr C-like. Da ich aber bevorzugter C++ler bin, möchte ich natürlich auch auf so einige C++-typischen Funktionen, wie cout, zurückgreifen.
    Und genau hier gibt es ein Problem und zwar in der Ausgabe.
    Die obigen drei Quelltextdateien "helloworld2-4.cpp" sind extra Abwandlungen der "helloworld1.cpp", nur eben zu mehr C++-Code hin optimiert.
    Sie lassen sich auch kompilieren und ausführen, alles ohne Fehler oder Warnungen. Die "helloworld3-4.cpp" gibts auch nur deshalb, weil ich da so eine Vermutung hatte und im Quelltext verschiedene Programme ausprobieren wollte, zumal diese auf bestehende Webreferenzen beruhen.

    Lange Rede, kurzer Sinn, hier der Output der drei anderen C++-OpenMP-Programme "helloworld2-4.cpp":

    Output - helloworld2 (mehrere Durchläufe):

    ::::::::::::::
    helloworld2 - Durchlauf 1
    ::::::::::::::
    I am thread I am thread 7 of 24
    13 of 24
    I am thread 3 of 24
    I am thread 22 of 24
    I am thread 15 of 24
    I am thread 6 of 24
    I am thread 14 of 24
    I am thread 2 of 24
    I am thread 17 of 24
    I am thread 19 of 24
    I am thread 4 of 24
    I am thread 1 of 24
    I am thread 21 of 24
    I am thread 8 of 24
    I am thread 20 of 24
    I am thread 16 of 24
    I am thread 23 of 24
    I am thread 9 of 24
    I am thread 11 of 24
    I am thread 18 of 24
    I am thread 24 of 24
    I am thread 10 of 24
    I am thread 5 of 24
    I am thread 12 of 24
    ::::::::::::::
    helloworld2 - Durchlauf 2
    ::::::::::::::
    I am thread I am thread I am thread I am thread 10 of 7 of 24246I am thread
    I am thread I am thread 20 of 24
    I am thread of I am thread 1 of 24
    I am thread 8 of 24
    I am thread 24
    I am thread I am thread 13 of 24
    214 of I am thread I am thread 11 of 24
    24 of 24
    18 of 24
    2422 of 24
    5 of 24

    of 24

    I am thread 15 of 24
    I am thread 17 of 24
    I am thread 9 of 24
    I am thread 12 of 24
    I am thread 2 of 24
    I am thread 19 of 24
    3 of 24
    I am thread 16 of 24
    I am thread 14 of 24
    I am thread 23 of 24
    ::::::::::::::
    helloworld2 - Durchlauf 3
    ::::::::::::::
    I am thread I am thread I am thread 142422 of of 2424
    I am thread of 24
    I am thread
    I am thread 9 of 24
    I am thread 16 of 24
    I am thread 21 of 2415
    I am thread of I am thread 12I am thread 24
    23 of 24
    of 8I am thread 24
    of 124 of 1324 of
    24

    I am thread I am thread 219 of 24
    of 24
    I am thread 3 of 24
    I am thread 17 of 24
    I am thread 5 of 24
    I am thread 18 of 24
    I am thread 20 of 24
    I am thread 10 of 24
    I am thread 7 of 24
    I am thread 6 of 24
    I am thread 11 of 24
    I am thread 4 of 24

    Output - helloworld3 (mehrere Durchläufe):

    ::::::::::::::
    helloworld3 - Durchlauf 1
    ::::::::::::::
    I am thread 22 of 24
    I am thread 22 of 24
    I am thread 22 of 24
    I am thread 22 of 24
    I am thread 22 of 24
    I am thread 22 of 24
    I am thread 22 of 24
    I am thread 22 of 24
    I am thread 22 of 24
    I am thread 22 of 24
    I am thread 22 of 24
    I am thread 22 of 24
    I am thread 22 of 24
    I am thread 22 of 24
    I am thread 22 of 24
    I am thread 22 of 24
    I am thread 22 of 24
    I am thread 22 of 24
    I am thread 22 of 24
    I am thread 22 of 24
    I am thread 22 of 24
    I am thread 22 of 24
    I am thread 22 of 24
    I am thread 22 of 24
    ::::::::::::::
    helloworld3 - Durchlauf 2
    ::::::::::::::
    I am thread I am thread 11 of 24
    11 of 24
    I am thread 11 of 24
    I am thread 11 of 24
    I am thread 11 of 24
    I am thread 11 of 24
    I am thread 11 of 24
    I am thread 11 of 24
    I am thread 11 of 24
    I am thread 11 of 24
    I am thread 11 of 24
    I am thread 11 of 24
    I am thread 11 of 24
    I am thread 11 of 24
    I am thread 11 of 24
    I am thread 11 of I am thread 2411 of 24
    I am thread 11 of 24
    I am thread 11 of 24
    I am thread 20 of 24
    I am thread I am thread 20 of 24
    I am thread 20 of 24
    20 of 24

    I am thread 20 of 24
    ::::::::::::::
    helloworld3 - Durchlauf 3
    ::::::::::::::
    I am thread I am thread I am thread 77 of 24
    7 of of 24
    24
    I am thread I am thread I am thread 7 of I am thread 7 of 24I am thread 247 of 24
    I am thread I am thread
    7 of 24
    I am thread 7 of 24

    7 of 24
    I am thread 7 of 24
    I am thread 7 of 24
    I am thread I am thread 77 of 24
    of 724
    7 of of 24
    24
    I am thread 7 of 24
    I am thread 7 of 24
    I am thread 23 of 24
    I am thread 21 of 24
    I am thread 21 of 24
    I am thread 22 of 24
    I am thread 22 of 24
    I am thread 22 of 24
    I am thread 22 of 24

    Output - helloworld4 (mehrere Durchläufe):

    ::::::::::::::
    helloworld4 - Durchlauf 1
    ::::::::::::::
    I am thread I am thread I am thread I am thread 16 of 24
    822 of I am thread of 24
    4 of 24
    24
    I am thread 20 of 24
    I am thread 11 of 24
    7 of 24
    I am thread I am thread 10 of 24
    I am thread 21 of 24
    I am thread 6 of 24
    I am thread 5 of 24
    I am thread 14 of 24
    I am thread 2 of 24
    I am thread 15 of 24
    I am thread 17 of 24
    I am thread 1 of 24
    I am thread 13 of 24
    I am thread 9 of 24
    0 of 24
    I am thread 18 of 24
    I am thread 19 of 24
    I am thread 23 of 24
    I am thread 12 of 24
    I am thread 3 of 24
    ::::::::::::::
    helloworld4 - Durchlauf 2
    ::::::::::::::
    I am thread I am thread I am thread I am thread I am thread I am thread I am thread I am thread 1115 of 24
    19 of 1713 of 240I am thread 9 of 24
    I am thread of 24
    I am thread 7 of 242414 of 24
    I am thread
    of I am thread 10 of 24
    I am thread I am thread
    I am thread 1 of 24
    24
    6I am thread I am thread I am thread 22 of 212024 of of of
    24
    1224
    1824 of
    of 24
    24
    16 of 24
    I am thread 4 of 24
    I am thread 23 of 24
    of 24
    I am thread 2 of 24
    I am thread 8 of 24
    5 of 24

    I am thread 3 of 24
    ::::::::::::::
    helloworld4 - Durchlauf 3
    ::::::::::::::
    I am thread 9 of 24
    I am thread I am thread 13 of 24
    2 of 24
    I am thread 19 of 24
    I am thread 17 of 24
    I am thread 3 of I am thread 24I am thread
    I am thread I am thread 718 of 24
    I am thread I am thread 20 of 24
    I am thread of 24
    421 of 24
    22 of 24
    I am thread 14 of 24
    of 24
    1 of 24
    I am thread 12 of 24
    I am thread 16 of 24
    I am thread 0 of 24
    I am thread 5 of 24
    I am thread 10 of 24
    I am thread 23 of 24
    I am thread 15 of 24
    I am thread 11 of 24
    I am thread 6 of 24
    I am thread 8 of 24

    -------------------------------------------

    Ich weiß einfach nicht, wo da das Problem liegt.
    Sicherlich könnte ich einfach auf printf(,); ausweichen, aber das klärt nicht wirklich, warum es zu solchen "Fehlverhalten" kommt. Meine Vermutung liegt ja in der zeitlichen Ausführung jeden Threads und der Versuch dabei zur "selben" Zeit auf das gleiche Display etwas auszugeben. Ist cout denn um so vieles schneller und performanter als printf(,); bzw. umgekehrt oder ist meine Vermutung gänzlich falsch und die Ursache liegt wo anders?

    Aber warum funktionierts dann bei "helloworld1.cpp" und nicht bei den anderen? Insb. der Vergleich zwischen "helloworld1.cpp" und "helloworld2.cpp" interessiert mich, da sich diese beiden Programme lediglich in der Art der Output-Funktion unterscheiden: printf(,)<<-->>cout.

    Und warum unterscheiden sich im Output auch die drei cout-behafteten Programme "helloworld2-4.cpp"?
    Wie Ihr auch sehen könnt, geben die cout-behafteten Programme irgendwie nach Lust und Laune, immer wieder verschiedenen Outputs aus (sehet mehrere Durchläufe bei jedem der drei cout-Programme!).

    Realisiere ich die vier Programme allesamt mit printf(,), anstatt cout, funktionieren sie. Das stärkt mich noch mehr in der Vermutung, dass das irgendwie mit cout zusammenhängen muss.

    Ich wäre für jeden Tipp und Lösungsansatz/-vorschlag und / oder Erklärung dankbar. Im Netz hab ich leider nichts dazu finden können, auch Google war kein Freund und Helfer dabei. 😉

    Grüße

    Schlitzauge 🙂 🙂 🙂 🙂


  • Mod

    Zu lang, habe nur überflogen. Entschuldige, wenn ich was wichtiges nicht gelsesen habe:

    Die Erklärung ist ganz einfach: printf ist in der Standardbibliothek des gcc threadsicher, die iostreams nicht. Was man dagegen normalerweise macht ist, dass nur ein Thread die Ausgaben macht (die Ausgaben in diesem Beispiel sind ziemlich unnötig, in der Praxis gibt man ja oft nur am Ende die Ergebnisse aus) oder, wenn es denn unbedingt sein muss, du machst einen lock auf das cout-Objekt. Wie das mit den locks geht, kommt gewiss bald in deinem Tutorial.

    edit: Wenn ich länger darüber nachdenke, wäre es vielelicht besser, die Ausgabe insgesamt in eine critical-section zu machen. Wer weiß, was sonst noch mit der Verkettung der operator<< passiert.



  • Jo, ich verstehe. Hab vielen Dank.
    Ja, ich weiß, dass dies ein eher schlechtes Anwendungs-Beispiel für Parallelität ist. Es sollte ja erstmal ein Anfangs-Prog mit OpenMP sein. Wie so oft, ist dies nun mal ein "Hello World"-Prog. 😉
    I.d.R. würde ich Parallelität sowieso nur für Nicht-User-I/O-Operationen machen, z.B. Berechnungenen.

    Woran sehe / merke ich eigentlich, ob eine Funktion threadsicher ist? Gibt es dazu Literatur / Quellen, wo man sich darüber informieren kann?
    Welche Funktionen der StdLib sind threadsicher und welche nicht? etc. etc.
    Soll Threadsicherheit bei den iostreams eigentlich noch kommen, gibt es dazu schon ein paar News oder?

    Grüße

    Schlitzauge 🙂 🙂 🙂 🙂


  • Mod

    Zuerst einmal ist keine Funktion der Standardbibliothek threadsicher. Es gibt ein paar Funktionen, bei denen du davon ausgehen kannst, dass sie ziemlich sicher threadsicher (im Sinne von reentrant) sind, weil sie einfach nichts verändern sollten und solche, die ganz sicher niemals threadsicher sind (z.B. große Teile der C-Bibliothek zu Zeichenketten). Wie das bei einer konkreten Funktion ist, kannst du oftmals mit Google rausfinden.

    Der 2011er C++ Standard macht teilweise Garantien für bestimmte Operationen, aber auch nicht so wirklich für die Standardfunktionen.



  • So, hab die Ausgabe nun soweit hinbekommen.

    Durch meine Recherchen stieß ich in der englischen Wikipedia auf folgenden Codeschnipsel:

    #include <omp.h>
    #include <iostream>
    int main (int argc, char *argv[]) {
      int th_id, nthreads;
      #pragma omp parallel private(th_id)
      {
        th_id = omp_get_thread_num();
        std::cout << "Hello World from thread " << th_id << std::endl;
        #pragma omp barrier
        #pragma omp master
        {
          nthreads = omp_get_num_threads();
          std::cout << "There are " << nthreads << " threads" << std::endl;
        }
      }
      return 0;
    }
    

    Auch dieses Prog funktioniert nicht. Ebenso "fehlerhafte" Ausgabe und da wird bereits eine Barriere angewandt. Warum das nicht funktionieren kann, liegt nach wie vor auf der Hand (Threadsicherheit != iostreams). 😉
    Da Frage ich mich nur, ob dieser Code bei Demjenigen funktioniert hat, als er den bei Wiki postete???

    Also hab ich mich etwas weiter belesen und das ganze mit einer kritischen Zone gelöst:

    Mein Prog (korrigiert) "helloworld6.cpp":

    #ifdef _OPENMP
    #include <omp.h>
    #endif
    
    #include <stdlib.h>
    #include <stdio.h>
    #include <iostream>
    
    using namespace std;
    
    int main(int argc, char *argv[])
    {
      #pragma omp parallel
      {
        #pragma omp critical
        {
          cout << "Hello World, says thread " << omp_get_thread_num()+1 << " of " << omp_get_num_threads() 
    << ".\n";
        }
        #pragma omp barrier
      }
    
      getchar();
      return 0;
    }
    

    @SeppJ: Ich glaube, Du meintest mit Locks bestimmt das mit "Simple lLocks" oder?
    Das wär auch ne Lösung, muss ich mich aber noch weiter belesen. Inwieweit unterscheiden sich denn der Einsatz einer kritischen Zone (#pragma omp critical) und der Einsatz von Locks? Kommt da nicht das selbe heraus?

    --------------------------------------------

    Dann hättsch mal noch ne Frage. Die Barriere (#pragma omp barrier) aus meiner nun funktionierenden Version, rührt nur daher, weil ich mich zunächst daran gesetzt habe, die EN-Wikipedia-Version zu korrigieren. Ich habs dann auch glei bei EN-Wiki korrigiert:

    #include <omp.h>
    #include <iostream>
    
    using namespace std;
    
    int main(int argc, char *argv[])
    {
      int th_id, nthreads;
      #pragma omp parallel private(th_id) shared(nthreads)
      {
        th_id = omp_get_thread_num();
        #pragma omp critical
        {
          cout << "Hello World from thread " << th_id << '\n';
        }
        #pragma omp barrier
        #pragma omp master
        {
          nthreads = omp_get_num_threads();
          cout << "There are " << nthreads << " threads" << '\n';
        }
      }
    
      getchar();
      return 0;
    }
    

    Meine Frage ist nun, ob die Barriere in meinem Programm noch einen logischen Sinn macht oder ob ich die wieder entfernen soll? Ob mit oder ohne Barriere, das Prog funktioniert in jedem Fall. :p
    Ich weiß jetzt nicht, wie der Schrittbetrieb beim Einsatz einer kritischen Zone ausschaut. Grob betrachtet, müsste intern am Ende einer kritischen Zone bereits eine Barriere gesetzt sein, da ja nur jeweils ein Thread simultan diese kritische Zone ausführen soll und die anderen warten müssen.

    Mein ursprünglicher Gedanke war ja, dass ich die Barriere-Extra am Ende belasse, falls dem nicht so ist und ich so die kritische Zone erstmal von allen Threads nacheinander ausführen lasse. Es wäre natürlich unnötiger Code mehr, wenn ich mit meiner Vermutung falsch liege.

    THX nochmal.

    Grüße

    Schlitzauge 🙂 🙂 🙂


Anmelden zum Antworten