Füllen Sie Histogramme (array-Minderung) in parallele mit OpenMP, ohne einen kritischen Abschnitt
Möchte ich füllen Histogramme parallel mit OpenMP. Ich habe mit zwei verschiedenen Methoden, dies zu tun mit OpenMP in C/C++.
Die erste Methode proccess_data_v1
macht ein eigenes Histogramm variable hist_private
für jeden thread, füllt Sie in prallel, und dann Summen die private Histogramme in das gemeinsame Histogramm hist
im critical
Abschnitt.
Die zweite Methode proccess_data_v2
macht ein shared array von Histogrammen mit array-Größe, die gleich der Anzahl von Fäden, füllt dieses array parallel, und addiert das gemeinsame Histogramm hist
parallel.
Die zweite Methode scheint mir überlegen, da es vermeidet einen kritischen Abschnitt dar, und die Summe der Histogramme in parallel. Jedoch, es erfordert die Kenntnis der Anzahl der threads und aufrufen omp_get_thread_num()
. Ich in der Regel versuchen, dies zu vermeiden. Gibt es eine bessere Art und Weise zu tun, die zweite Methode, ohne Bezug auf den thread-zahlen und mit einem shared array mit Größe gleich der Anzahl der threads?
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 erstellt haben eine verbesserte Methode namens 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);
}
- Könnten Sie bitte erklären, warum Sie mit verschachtelten parallelen Regionen? (Ich beziehe mich auf Ihre process_data_v1-Ansatz). Vielleicht bin ich ja etwas nicht zu verstehen, aber nach dem code, es scheint mir, dass Sie Sie bitten, für Nthreads**2. Es ist zu sagen, Sie fordern mehr Ressourcen, als die verfügbaren. Ist das richtig? In anderen Worten, können Sie erklären, das Verhalten von parallelen Regionen innerhalb des äußeren ein? Danke...
Du musst angemeldet sein, um einen Kommentar abzugeben.
Könnte man reservieren Sie die große Auswahl innerhalb der parallelen region, wo man Abfragen kann über die tatsächliche Anzahl der threads verwendet werden:
Für eine bessere Leistung, würde ich raten, dass Sie Runde die Größe der einzelnen thread ' s chunk in
hista
auf ein Vielfaches der memory-Seite Größe, auch wenn dies möglicherweise Lücken lassen zwischen den unterschiedlichen Teil-Histogramme. Auf diese Weise verhindern Sie, dass sowohl false-sharing und remote-memory-access auf NUMA-Systeme (aber nicht in der reduktionsphase).posix_memalign
Speicher ausgerichtet auf eine Seite Grenze.mmap
s und daher immer frische Seiten. Aber es ist egal, welchen thread allokiert den Speicher. Es ist wichtig, die Lauffläche ersten Berührungen jede einzelne Seite - das aktuelle NUMA-Richtlinien auf Linux ist "first touch", also physische Speicher stammt von der Seite NUMA-Knoten, wo Sie den code, der zuerst berührt, dass die Seite läuft.