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;
}
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;
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:
Ces "problèmes de performances" sont de ma propre initiative et il est tout à fait simple de ne pas les introduire:
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}} )
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.