2013-05-28 6 views
8

Ich möchte Histogramme parallel mit OpenMP ausfüllen. Ich habe zwei verschiedene Methoden gefunden, dies mit OpenMP in C/C++ zu tun.Fill-Histogramme (Array-Reduktion) parallel zu OpenMP ohne Verwendung eines kritischen Abschnitts

Die erste Methode proccess_data_v1 ein eigenes Histogramm Variable für jeden Thread hist_private macht, füllt sie in prallel und summiert dann die privaten Histogramme in den gemeinsam benutzten Histogramms hist in einem critical Abschnitt.

Die zweite Methode proccess_data_v2 erstellt ein freigegebenes Array von Histogrammen mit Array-Größe gleich der Anzahl der Threads, füllt dieses Array parallel und summiert dann das gemeinsam genutzte Histogramm hist parallel.

Die zweite Methode scheint mir überlegen, da sie einen kritischen Abschnitt vermeidet und die Histogramme parallel summiert. Es erfordert jedoch, die Anzahl der Threads zu kennen und omp_get_thread_num() aufzurufen. Ich versuche das generell zu vermeiden. Gibt es einen besseren Weg, die zweite Methode zu verwenden, ohne auf die Thread-Nummern zu verweisen und ein gemeinsam genutztes Array zu verwenden, dessen Größe der Anzahl der Threads entspricht?

void proccess_data_v1(float *data, int *hist, const int n, const int nbins, float max) { 
    #pragma omp parallel 
    { 
     int *hist_private = new int[nbins]; 
     for(int i=0; i<nbins; i++) hist_private[i] = 0; 
     #pragma omp for nowait 
     for(int i=0; i<n; i++) { 
      float x = reconstruct_data(data[i]); 
      fill_hist(hist_private, nbins, max, x); 
     } 
     #pragma omp critical 
     { 
      for(int i=0; i<nbins; i++) { 
       hist[i] += hist_private[i]; 
      } 
     } 
     delete[] hist_private; 
    } 
} 

void proccess_data_v2(float *data, int *hist, const int n, const int nbins, float max) { 
    const int nthreads = 8; 
    omp_set_num_threads(nthreads); 
    int *hista = new int[nbins*nthreads]; 

    #pragma omp parallel 
    { 
     const int ithread = omp_get_thread_num(); 
     for(int i=0; i<nbins; i++) hista[nbins*ithread+i] = 0; 
     #pragma omp for 
     for(int i=0; i<n; i++) { 
      float x = reconstruct_data(data[i]); 
      fill_hist(&hista[nbins*ithread], nbins, max, x); 
     } 

     #pragma omp for 
     for(int i=0; i<nbins; i++) { 
      for(int t=0; t<nthreads; t++) { 
       hist[i] += hista[nbins*t + i]; 
      } 
     } 

    } 
    delete[] hista; 
} 

Edit: Basierend auf einem Vorschlag von @HristoIliev ich ein verbessertes Verfahren process_data_v3

#define ROUND_DOWN(x, s) ((x) & ~((s)-1)) 
void proccess_data_v2(float *data, int *hist, const int n, const int nbins, float max) { 
    int* hista; 
    #pragma omp parallel 
    { 
     const int nthreads = omp_get_num_threads(); 
     const int ithread = omp_get_thread_num(); 

     int lda = ROUND_DOWN(nbins+1023, 1024); //1024 ints = 4096 bytes -> round to a multiple of page size 
     #pragma omp single 
     hista = (int*)_mm_malloc(lda*sizeof(int)*nthreads, 4096); //align memory to page size 

     for(int i=0; i<nbins; i++) hista[lda*ithread+i] = 0; 
     #pragma omp for 
     for(int i=0; i<n; i++) { 
      float x = reconstruct_data(data[i]); 
      fill_hist(&hista[lda*ithread], nbins, max, x); 
     } 

     #pragma omp for 
     for(int i=0; i<nbins; i++) { 
      for(int t=0; t<nthreads; t++) { 
       hist[i] += hista[lda*t + i]; 
      } 
     } 

    } 
    _mm_free(hista); 
} 
+0

Könnten Sie bitte erläutern, warum Sie verschachtelte parallele Regionen verwenden? (Ich beziehe mich auf Ihren process_data_v1-Ansatz). Vielleicht verstehe ich etwas nicht, aber gemäß deinem Code scheint es mir, dass du nach Nthreads fragst ** 2. Es bedeutet, dass Sie nach mehr Ressourcen als den verfügbaren Ressourcen fragen. Ist das korrekt? Mit anderen Worten, könnten Sie das Verhalten von parallelen Regionen innerhalb der äußeren erklären? Danke ... – Alejandro

Antwort

3

Sie genannt erstellt haben, könnte das große Array innerhalb der parallelen Region zuzuordnen, in dem Sie über abfragen die tatsächliche Anzahl der verwendeten Threads:

int *hista; 
#pragma omp parallel 
{ 
    const int nthreads = omp_get_num_threads(); 
    const int ithread = omp_get_thread_num(); 

    #pragma omp single 
    hista = new int[nbins*nthreads]; 

    ... 
} 
delete[] hista; 

Für bessere Leistung I w Es empfiehlt sich, die Größe des Chunks jedes Threads in hista auf ein Vielfaches der Speicherseitengröße des Systems zu runden, auch wenn dies möglicherweise Löcher zwischen den verschiedenen Teilhistogrammen hinterlassen könnte. Auf diese Weise verhindern Sie sowohl die falsche Freigabe als auch den Remote-Speicherzugriff auf NUMA-Systeme (jedoch nicht in der endgültigen Reduzierungsphase).

+0

Danke. Ich habe Ihren Vorschlag umgesetzt und es ist definitiv eine bessere Lösung. Ich muss über die Seitengröße nachlesen. Ich dachte, dass die Chunks in hista ein Vielfaches der Cache-Zeilengröße (64 Bytes) wären, um falsches Teilen zu verhindern. Wenn zum Beispiel nbins ein Vielfaches von 64 ist (und die Adresse von hista auch ein Vielfaches von 64 wäre), würde das falsche Weitergabe nicht verhindern? –

+0

@Hristolliev, ich habe Code mit Ihren Vorschlägen hinzugefügt. Ich nannte die Chuck-Größe lda und machte es zu einem Vielfachen von 64. Sollte ich einen anderen Wert verwenden, z. 4 KB = Seitengröße? –

+0

Wenn Sie auf einem NUMA-System, z. ein Multisocket AMD64 oder eine moderne Xeon Maschine, dann sollten Sie auf 4 KiB runden. Sobald die korrekt gerundeten Größen bestimmt sind, verwenden Sie 'posix_memignign', um den auf einer Seitengrenze ausgerichteten Speicher zuzuordnen. –