web-dev-qa-db-fra.com

Comprendre la clause collapse dans openmp

Je suis tombé sur un code OpenMP contenant la clause collapse, ce qui était nouveau pour moi. J'essaie de comprendre ce que cela signifie, mais je ne pense pas avoir bien compris ses implications. Une définition que j'ai trouvée est:

COLLAPSE : Spécifie le nombre de boucles d'une boucle imbriquée qui doivent être réduites en un grand espace d'itération et divisées en fonction de la clause schedule. L'exécution séquentielle des itérations dans toutes les boucles associées détermine l'ordre des itérations dans l'espace d'itération réduit.

Je pensais comprendre ce que cela voulait dire, alors j’ai essayé le programme simple suivant:

int i, j;
#pragma omp parallel for num_threads(2) private(j)
for (i = 0; i < 4; i++)
    for (j = 0; j <= i; j++)
        printf("%d %d %d\n", i, j, omp_get_thread_num());

Qui produit

0 0 0
1 0 0
1 1 0
2 0 0
2 1 0
2 2 1
3 0 1
3 1 1
3 2 1
3 3 1

J'ai ensuite ajouté la clause collapse(2). Je m'attendais à avoir le même résultat dans les deux premières colonnes mais maintenant un nombre égal de 0 et de 1 dans la dernière colonne . Mais j'ai

0 0 0
1 0 0
2 0 1
3 0 1

Donc mes questions sont:

  1. Qu'est-ce qui se passe dans mon code?
  2. Dans quelles circonstances devrais-je utiliser collapse?
  3. Pouvez-vous fournir un exemple montrant la différence entre utiliser collapse et ne pas l'utiliser?
24
iomartin

Le problème avec votre code est que les itérations de la boucle interne dépendent de la boucle externe. Selon la spécification OpenMP sous la description de la section sur la liaison et de la clause collapse:

Si l'exécution d'une boucle associée change l'une des valeurs utilisées pour calculer une de l'itération compte, alors le comportement est indéterminé.

Vous pouvez utiliser collapse quand ce n'est pas le cas, par exemple avec une boucle carrée

#pragma omp parallel for private(j) collapse(2)
for (i = 0; i < 4; i++)
    for (j = 0; j < 100; j++)

En fait, c’est un bon exemple pour montrer quand utiliser l’effondrement. La boucle externe n'a que quatre itérations. Si vous avez plus de quatre threads, certains seront gaspillés. Mais lorsque vous réduisez les threads, ils seront répartis sur 400 itérations, ce qui sera probablement beaucoup plus grand que le nombre de threads. Une autre raison d'utiliser collapse est si la charge n'est pas bien distribuée. Si vous avez utilisé seulement quatre itérations et que la quatrième a pris la plupart du temps, les autres threads attendent. Mais si vous utilisez 400 itérations, la charge sera probablement mieux répartie.

Vous pouvez fusionner une boucle à la main pour le code ci-dessus comme ceci

#pragma omp parallel for
for(int n=0; n<4*100; n++) {
    int i = n/100; int j=n%100;

Ici est un exemple montrant comment fusionner une boucle triplement fusionnée à la main.

Enfin, ici est un exemple montrant comment fusionner une boucle triangulaire pour laquelle collapse n'est pas défini.


Voici une solution qui mappe une boucle rectangulaire à la boucle triangulaire dans la question des OP. Ceci peut être utilisé pour fondre la boucle triangulaire des OP.

//int n = 4;
for(int k=0; k<n*(n+1)/2; k++) {
    int i = k/(n+1), j = k%(n+1);
    if(j>i) i = n - i -1, j = n - j;
    printf("(%d,%d)\n", i,j);
}

Cela fonctionne pour toute valeur de n.

La carte pour la question des PO va de

(0,0),
(1,0), (1,1),
(2,0), (2,1), (2,2),
(3,0), (3,1), (3,2), (3,3),

à

(0,0), (3,3), (3,2), (3,1), (3,0),
(1,0), (1,1), (2,2), (2,1), (2,0),

Pour les valeurs impaires de n, la carte n'est pas exactement un rectangle mais la formule fonctionne toujours.

Par exemple, n = 3 est mappé à partir de 

(0,0),
(1,0), (1,1),
(2,0), (2,1), (2,2),

à 

(0,0), (2,2), (2,1), (2,0),
(1,0), (1,1),

Voici le code pour tester cela

#include <stdio.h>
int main(void) {
    int n = 4;
    for(int i=0; i<n; i++) {
        for(int j=0; j<=i; j++) {
            printf("(%d,%d)\n", i,j);
        }
    }
    puts("");
    for(int k=0; k<n*(n+1)/2; k++) {
        int i = k/(n+1), j = k%(n+1);
        if(j>i) i = n - i - 1, j = n - j;
        printf("(%d,%d)\n", i,j);
    }
}
28
Z boson

Si votre objectif consiste à équilibrer la charge sur des lignes croissantes, en supposant que la charge de travail de chaque élément soit régulière ou bien dispersée, pourquoi ne pas scinder les index des lignes en deux et oublier la clause collapse?

#pragma omp for
for (int iy0=0; iy0<n; ++iy0){
  int iy = iy0;
  if (iy0 >= n/2) iy = n-1 -iy0 +n/2;
  for (int ix=iy+1; ix<n; ++ix){
    work(ix, iy);
  }
}
0
h2kyeong