web-dev-qa-db-fra.com

Réduction sur la baie dans OpenMP

J'essaie de paralléliser le programme suivant, mais je ne sais pas comment réduire sur un tableau. Je sais que ce n'est pas possible, mais existe-t-il une alternative? Merci. (J'ai ajouté une réduction sur m, ce qui est faux, mais j'aimerais avoir un conseil sur la façon de le faire.)

#include <iostream>
#include <stdio.h>
#include <time.h>
#include <omp.h>
using namespace std;

int main ()
{
  int A [] = {84, 30, 95, 94, 36, 73, 52, 23, 2, 13};
  int S [10];

  time_t start_time = time(NULL);
  #pragma omp parallel for private(m) reduction(+:m)
  for (int n=0 ; n<10 ; ++n ){
    for (int m=0; m<=n; ++m){
      S[n] += A[m];
    }
  }
  time_t end_time = time(NULL);
  cout << end_time-start_time;

  return 0;
}
27
user2891902

Oui, il est possible de réduire les baies avec OpenMP. À Fortran, il a même construit pour cela. En C/C++, vous devez le faire vous-même. Voici deux façons de procéder.

La première méthode crée une version privée de S pour chaque thread, les remplit en parallèle, puis les fusionne dans S dans une section critique (voir le code ci-dessous). La deuxième méthode fait un tableau avec des dimensions 10 * nthreads. Remplit ce tableau en parallèle, puis le fusionne dans S sans utiliser de section critique. La deuxième méthode est beaucoup plus compliquée et peut avoir des problèmes de cache, en particulier sur les systèmes multi-socket si vous ne faites pas attention. Pour plus de détails, voir ceci Remplir les histogrammes (réduction de la matrice) en parallèle avec OpenMP sans utiliser de section critique

Première méthode

int A [] = {84, 30, 95, 94, 36, 73, 52, 23, 2, 13};
int S [10] = {0};
#pragma omp parallel
{
    int S_private[10] = {0};
    #pragma omp for
    for (int n=0 ; n<10 ; ++n ) {
        for (int m=0; m<=n; ++m){
            S_private[n] += A[m];
        }
    }
    #pragma omp critical
    {
        for(int n=0; n<10; ++n) {
            S[n] += S_private[n];
        }
    }
}

Deuxième méthode

int A [] = {84, 30, 95, 94, 36, 73, 52, 23, 2, 13};
int S [10] = {0};
int *S_private;
#pragma omp parallel
{
    const int nthreads = omp_get_num_threads();
    const int ithread = omp_get_thread_num();

    #pragma omp single 
    {
        S_private = new int[10*nthreads];
        for(int i=0; i<(10*nthreads); i++) S_private[i] = 0;
    }
    #pragma omp for
    for (int n=0 ; n<10 ; ++n )
    {
        for (int m=0; m<=n; ++m){
            S_private[ithread*10+n] += A[m];
        }
    }
    #pragma omp for
    for(int i=0; i<10; i++) {
        for(int t=0; t<nthreads; t++) {
            S[i] += S_private[10*t + i];
        }
    }
}
delete[] S_private;
28
Z boson

J'ai deux remarques concernant la réponse de Zboson:
1. La méthode 1 est certainement correcte, mais la boucle de réduction est en fait exécutée en série, en raison de la # pragma omp critique qui est bien sûr nécessaire car les matrices partielles sont locales à chaque thread et la réduction correspondante doit être fait par le fil qui doit la matrice.
2. Méthode 2: la boucle d'initialisation peut être déplacée en dehors de la section unique et devenir ainsi parallélisable.

Le programme suivant implémente la réduction du tableau à l'aide de la fonction de réduction définie par l'utilisateur openMP v4.0 :

/* Compile with:
     gcc -Wall -fopenmp -o ar ar.c
   Run with:
     OMP_DISPLAY_ENV=TRUE OMP_NUM_THREADS=10 OMP_NESTED=TRUE ./ar
*/
#include <stdio.h>
#include <omp.h>
struct m10x1 {int v[10];};
int A [] =       {84, 30, 95, 94, 36, 73, 52, 23, 2, 13};  
struct m10x1 S = {{ 0,  0,  0,  0,  0,  0,  0,  0, 0,  0}};
int n,m=0;

void print_m10x1(struct m10x1 x){
  int i;
  for(i=0;i<10;i++) printf("%d ",x.v[i]);
  printf("\n");
}

struct m10x1 add_m10x1(struct m10x1 x,struct m10x1 y){
  struct m10x1 r ={{ 0,  0,  0,  0,  0,  0,  0,  0, 0,  0}};
  int i;
  for (i=0;i<10;i++) r.v[i]=x.v[i]+y.v[i];
  return r;
}

#pragma omp declare reduction(m10x1Add: struct m10x1: \
omp_out=add_m10x1(omp_out, omp_in)) initializer( \
omp_priv={{ 0,  0,  0,  0,  0,  0,  0,  0, 0,  0}} )

int main ()
{
  #pragma omp parallel for reduction(m10x1Add: S)
  for ( n=0 ; n<10 ; ++n )
    {
      for (m=0; m<=n; ++m){
        S.v[n] += A[m];
      }
    }
  print_m10x1(S);
}

Cela suit textuellement l'exemple de réduction de nombre complexe à la page 97 de fonctionnalités OpenMP 4. .

Bien que la version parallèle fonctionne correctement, il y a probablement des problèmes de performances que je n'ai pas étudiés:

  1. les entrées et sorties add_m10x1 sont passées par valeur.
  2. La boucle dans add_m10x1 est exécutée en série.

Ces "problèmes de performances" sont de ma propre initiative et il est tout à fait simple de ne pas les introduire:

  1. Les paramètres à add_m10x1 doivent être passés par référence (via des pointeurs en C, des références en C++)
  2. Le calcul dans add_m10x1 doit être fait sur place.
  3. add_m10x1 doit être déclaré nul et la déclaration de retour supprimée. Le résultat est renvoyé via le premier paramètre.
  4. Le pragma de réduction de déclaration doit être modifié en conséquence, le combineur ne doit être qu'un appel de fonction et non une affectation (spécifications v4.0 p181 lignes 9,10).
  5. La boucle for dans add_m10x1 peut être parallélisée via un parallèle omp pour pragma
  6. L'imbrication parallèle doit être activée (par exemple via OMP_NESTED = TRUE)

La partie modifiée du code est alors:

void add_m10x1(struct m10x1 * x,struct m10x1 * y){
  int i;
  #pragma omp parallel for
  for (i=0;i<10;i++) x->v[i] += y->v[i];
}

#pragma omp declare reduction(m10x1Add: struct m10x1: \
add_m10x1(&omp_out, &omp_in)) initializer( \
omp_priv={{ 0,  0,  0,  0,  0,  0,  0,  0, 0,  0}} )
9
NameOfTheRose

Si la traduction de votre code en Fortran, qui peut utiliser des tableaux dans les opérations de réduction OpenMP, ne fait pas appel, vous pouvez utiliser un tas de variables temporaires. Par exemple

int S0, S1, S2, ..., S9;
...
#pragma omp parallel for private(...) shared(S0, S1, S2, ..., S9) \
            reduction(+:S0, S1, S2, ..., S9)
for ...

Cela vous laisse avec la perspective peu attrayante d'avoir à écrire une sorte d'instruction if ou case pour déterminer laquelle des temporaires doit être mise à jour. Si votre code n'est qu'un exemple que vous souhaitez utiliser pour l'apprentissage, continuez.

Mais si votre intention est vraiment d'écrire une routine de somme de préfixes parallèles, recherchez autour. C'est un bon point de départ.

0