2012-10-18 11 views
5

Ich habe eine große Schleife, die Daten generiert. Jede Iteration dauert beispielsweise 1 Sekunde und erzeugt einen Datenblock. Ich brauche alle Stücke in der richtigen Reihenfolge in die Datei geschrieben.OpenMP-Synchronisation innerhalb der Schleife

Wenn ich nur die Schleife parallelisieren wollte, könnte ich so etwas wie dies (stark vereinfacht) schreiben:

FILE* f = fopen("output.txt", "w"); 
    omp_lock_t lock; 
    omp_init_lock(&lock); 
    int nIterations = 1000000; 
#pragma omp parallel for 
    for(int thread=0; thread<4; thread++) 
    { 
     int a=0, b=0, c=0; 
     for(int n=thread; n<nIterations; n+=4) 
     { 
      int value = do_computations(&a, &b, &c); 
      omp_set_lock(&lock); 
      fprintf(f, "%d\n", value); 
      omp_unset_lock(&lock); 
     } 
    } 
#pragma omp barrier 
    fclose(f); 
    omp_destroy_lock(&lock); 

Das ist meine Ausgabe in die Datei bekommt, aber die Reihenfolge der Einträge nicht garantiert werden.

Ich möchte die Ausführung synchronisieren, so dass alle Threads ihre Aufgaben ausführen, dann der Master-Thread in die Datei schreibt und dann Threads fortsetzen. Mit anderen Worten, würde ich so etwas wie dies mag:

#pragma omp parallel for 
     for(int thread=0; thread<4; thread++) 
     { 
      int a=0, b=0, c=0; 
      int values[4]; 
      for(int n=thread; n<nIterations; n+=4) 
      { 
       values[n] = do_computations(&a, &b, &c); 
#pragma omp barrier 
       if(thread == 0) 
       { 
         for(int i=0; i<4; i++) 
         fprintf(f, "%d\n", values[i]); 
       } 
#pragma omp barrier 
      } 
     } 
#pragma omp barrier 

Außer aus unerklärlichen Gründen, dies durch die OpenMP-Spezifikation ist verboten.

Oder ich könnte

#pragma omp parallel for 
     for(int thread=0; thread<4; thread++) 
     { 
      int a=0, b=0, c=0; 
      for(int n=thread; n<nIterations; n+=4) 
      { 
       int value = do_computations(&a, &b, &c); 
#pragma omp ordered 
       { 
        fprintf(f, "%d\n", value); 
       } 
      } 
     } 
    #pragma omp barrier 
     fclose(f); 

versuchen Aber das wird auch nicht funktionieren, weil „eine Iteration einer Schleife mit einem für Konstrukt ... darf nicht mehr als eine geordnete Richtlinie auszuführen.“

Ich möchte den Code nicht als einzelne Schleife umschreiben und ich möchte keine Schleifen austauschen.

Gibt es einen sauberen Weg, dies mit OpenMP zu tun, ohne andere Threading/Synchronisationstools?

+0

In welcher Architektur/in welchem ​​Betriebssystem läuft der Code? – Raj

+0

Kannst du '#pragma omp parallel' statt' parallel für' verwenden? – Raj

+0

Ist 'do_computations' wirklich drei' 0's? Ich nehme an, 'do_computations' ist keine reine Funktion (d. H. Es hat Nebenwirkungen). Wenn ja, was sind die Nebenwirkungen von 'do_computations'? Was passiert, wenn zwei Aufrufe von 'do_computations' parallel ausgeführt werden? Ich bezweifle stark, dass es sogar möglich ist, sie parallel auszuführen (basierend auf der Annahme, dass es Nebenwirkungen gibt und daher die Reihenfolge, in der die Exekutionen stattfinden * wichtig ist *). - Oder vereinfachst du den Code zu sehr? Vielleicht solltest du etwas teilen, das deine echte Schleife besser repräsentiert? – ArjunShankar

Antwort

3

Sie versuchen, zwei Dinge zu tun - eine Berechnung und IO. Die Berechnung kann parallelisiert werden, aber die IO muss notwendigerweise seriell sein. Aber indem Sie die IO in die gleiche Schleife wie die Berechnung setzen, erzwingen Sie Serialisierung auch bei der Berechnung, was keinen Sinn ergibt.

Sie wären viel besser dran, die gesamte Berechnung zu tun, dann macht die IO. Dies wird mit ziemlicher Sicherheit auch in serieller Form schneller sein, besonders wenn Sie die Daten in binärer Form in einem großen Chunk schreiben können, anstatt mit einer Schleife über fprintfs.

FILE* f = fopen("output.txt", "w"); 
    const int nIterations = 1000000; 
    int values[nIterations]; 

#pragma omp parallel for 
    for(int n=0; n<niterations; n++) 
    { 
     int a=0, b=0, c=0; 
     values[n] = do_computations(&a, &b, &c); 
    } 

    for (int n=0; n<niterations; n++) 
     fprintf(f,"%d\n", values[n]); 

    fclose(f); 

Dies erfordert natürlich mehr Speicher, aber dann Geschwindigkeit gegen Speicher ist ein üblicher Kompromiss. Wenn die Extreme dieser Kompromiss nicht funktionieren, können Sie immer tun, um die Berechnung in einstellbaren großen Brocken:

const int nIterations = 1000000; 
    const int chunkSize = 10000; 
    int values[chunkSize]; 
    int chunkNum = 0; 
    int chunkLeft = chunkSize; 

    for (int start = 0; start < nIterations; start+= chunkSize) { 

     if (start+chunkSize > nIterations) chunkLeft = nIterations - start; 

    #pragma omp parallel for 
     for(int n=start; n<start+chunkLeft; n++) 
     { 
      int a=0, b=0, c=0; 
      values[n-start] = do_computations(&a, &b, &c); 
     } 

     for (int n=0; n<chunkLeft; n++) 
      fprintf(f,"%d\n", values[n]); 

    } 
    fclose(f); 
+0

Wir sprechen über die Situation, in der es notwendig ist, Berechnungen und I/O zu überlappen. Sicher, es ist normalerweise einfacher, einen anderen zu tun. Bis Sie 12 Stunden Rechenzeit verloren haben, weil es einen Stromausfall gab und alle Ergebnisse im RAM zwischengespeichert wurden, bis die Schleife abgeschlossen war. – user434507

+0

Das ist großartig, aber Sie überschneiden nicht die Kommunikation und die Berechnung, Sie serialisieren nur Ihre Berechnungen, wie sogar eine kursorische Profilierung zeigen würde. Wenn Sie die beiden überlappen möchten, erstellen Sie eine separate E/A-Task und verwenden Sie einen Producer/Consumer-Ansatz, um die Ausgabe zu puffern. –

+0

Ich bin nicht Serialisierung Berechnung, I/O dauert 0,01% der Zeit, den Rest der Zeit Threads parallel ausgeführt werden. Du überlegst das. Was ich in der ursprünglichen Frage geschrieben habe, ist genau das, was ich brauche. – user434507

0

Ich werde versuchen, eine Lösung nicht bereits in früheren Antworten vorzuschlagen:

#include <stdio.h> 
#include <assert.h> 
#include <unistd.h> 

#define NITER 100 

int main() { 

    FILE * f = fopen("output.bin", "w+"); 

#pragma omp parallel 
    { 
#pragma omp for schedule(runtime) 
    for (int ii = 0; ii < NITER; ++ii) {  
     sleep(1); // Simulate computation 
     printf("%d\n",ii); // Just to be convinced that the loop is not evaluated in serial order 
#pragma omp critical (FILEWRITE) 
     { 
    fseek (f ,sizeof(ii)*ii,SEEK_SET); 
    fwrite(&ii,sizeof(ii),1,f); 
     }  
    } 
    } 

    // Check serially that the file is written in the right order 
    fseek(f,0,SEEK_SET); 
    int value = -1; 
    for (int ii = 0; ii < NITER; ++ii) {   
    fread (&value,sizeof(ii),1,f);  
    assert(value == ii); 
    } 

    fclose(f); 
    return 0; 
} 

Dieser Fall gilt nur, wenn jeder Chunk eine sehr gut definierte Größe hat, sodass Sie, wenn Sie wissen, welche Iteration Sie berechnen, den Offset vom Anfang der Datei ableiten können.

Das heißt, in den Code-Snippets, die Sie bereitstellen, gibt es viele Fehler, die darauf hindeuten, dass Sie die Grundlagen von OpenMP überprüfen müssen. Zum Beispiel:

#pragma omp parallel for 
    for(int thread=0; thread<4; thread++) 
    { // No need to unroll the loop as OpenMP runtime 
     // map iterations on threads based on the scheduling policy 
     int a=0, b=0, c=0; 
     for(int n=thread; n<nIterations; n+=4) 
     { 
      int value = do_computations(&a, &b, &c); 
      // No need to use lock, when a critical construct suffices 
      omp_set_lock(&lock); 
      fprintf(f, "%d\n", value); 
      omp_unset_lock(&lock); 
     } 
    } // Implicit barrier at the end of the parallel for 
#pragma omp barrier 
// Why a barrier when there is only one thread? 
+0

Gibt es einen Vorteil von kritischen über das Schloss? Critical gibt einen etwas kürzeren Code, aber beide Optionen sollten auf identische Weise funktionieren. – user434507

+0

@ user434507 Der große Unterschied liegt in der Verwendung von fseek, um zu vermeiden, dass das Worksharing-Konstrukt als "geordnet" deklariert wird (was aufgrund meiner Erfahrung zu einer großen Verlangsamung führen kann). Anderenfalls vermeidet die Verwendung von '#pragma omp kritisch 'eine unnötige Abhängigkeit von' omp.h '. Wenn Sie also eine serielle Version der Binärdatei kompilieren möchten, müssen Sie Ihre Quellen nicht mit' #ifdef _OPENMP' füllen. – Massimiliano

+0

Ja, es ist ein interessanter Ansatz, der leider nicht hilft, wenn ich die Stückgröße nicht vorher weiß. In Windows müssen Sie omp.h trotzdem hinzufügen oder Sie erhalten Linker-Fehler. – user434507

0

spät zur Party, aber wenn jemand hier stolpert die Suche nach Antworten, was Sie brauchen, ist #pragma omp single, aber eine Diskussion mit @ jonathan-dursi als auch sehen.

Verwandte Themen