Füllen Sie Histogramme (Array-Reduzierung) parallel mit OpenMP, ohne einen kritischen Abschnitt zu verwenden
Ich möchte Histogramme parallel mit OpenMP füllen. Ich habe mir zwei verschiedene Methoden ausgedacht, um dies mit OpenMP in C / C ++ zu tun.
Die erste Methodeproccess_data_v1
Erstellt eine private Histogrammvariablehist_private
Füllt sie für jeden Thread parallel aus und summiert dann die privaten Histogramme in das gemeinsame Histogrammhist
in einemcritical
Sektion.
Die zweite Methodeproccess_data_v2
erstellt ein gemeinsames Array von Histogrammen mit einer Arraygröße, die der Anzahl der Threads entspricht, füllt dieses Array parallel und summiert dann das gemeinsame Histogrammhist
parallel zu.
Die zweite Methode erscheint mir überlegen, da sie einen kritischen Abschnitt vermeidet und die Histogramme parallel summiert. Es erfordert jedoch die Kenntnis der Anzahl der Threads und des Aufrufsomp_get_thread_num()
. Ich versuche im Allgemeinen, dies zu vermeiden. Gibt es eine bessere Möglichkeit, die zweite Methode durchzuführen, ohne die Thread-Nummern zu referenzieren und ein gemeinsames Array mit einer Größe zu verwenden, die 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;
}
Bearbeiten: Basierend auf einem Vorschlag von @HristoIliev habe ich eine verbesserte Methode namens erstelltprocess_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);
}