Outils pour utilisateurs

Outils du site


lang:cpp:openmp

Différences

Ci-dessous, les différences entre deux révisions de la page.

Lien vers cette vue comparative

Les deux révisions précédentesRévision précédente
Prochaine révision
Révision précédente
lang:cpp:openmp [2019/03/10 16:10] – Complétion rootlang:cpp:openmp [2022/09/05 23:05] (Version actuelle) – [linear] : fix format root
Ligne 1: Ligne 1:
-====Compilateur====+====Compilation==== 
 +===Compilateur===
 Pour activer l'option : ''-openmp'' pour ''GCC'', ''/openmp'' pour Visual Studio (''Projet''|''Propriétés''|''C/C++''|''Langage''|''Prise en charge de OpenMP''). Pour activer l'option : ''-openmp'' pour ''GCC'', ''/openmp'' pour Visual Studio (''Projet''|''Propriétés''|''C/C++''|''Langage''|''Prise en charge de OpenMP'').
  
 [[https://www.openmp.org/wp-content/uploads/openmp-4.5.pdf|OpenMP Application Programming Interface (Norme OpenMP v4.5)]] {{ :lang:cpp:openmp:openmp-4.5.pdf |Archive du 09/03/2019}} [[https://www.openmp.org/wp-content/uploads/openmp-4.5.pdf|OpenMP Application Programming Interface (Norme OpenMP v4.5)]] {{ :lang:cpp:openmp:openmp-4.5.pdf |Archive du 09/03/2019}}
  
-[[https://bisqwit.iki.fi/story/howto/openmp/|Guide into OpenMP: Easy multithreading programming for C++]] {{ :lang:cpp:openmp:guide_into_openmp_easy_multithreading_programming_for_c_.mhtml |Archive du 09/03/2019}}+[[https://www.openmp.org/wp-content/uploads/openmp-examples-4.5.0.pdf|OpenMP Application Programming Interface Examples]], {{ :lang:cpp:openmp:openmp-examples-4.5.0.pdf |Archive du 11/02/2019}} 
 + 
 +[[https://bisqwit.iki.fi/story/howto/openmp/|Guide into OpenMP: Easy multithreading programming for C++]] {{ :lang:cpp:openmp:guide_into_openmp_easy_multithreading_programming_for_c_2019-10-16_15_43_58_.html |Archive du 10/02/2018 le 16/10/2019}} 
 + 
 +===CMake=== 
 + 
 +<code cmake> 
 +find_package(OpenMP) 
 +if(OpenMP_CXX_FOUND) 
 +  target_link_libraries(MyTarget PUBLIC OpenMP::OpenMP_CXX) 
 +endif() 
 +</code> 
 + 
 +[[https://cliutils.gitlab.io/modern-cmake/modern-cmake.pdf|Modern CMake]] {{ :lang:cpp:openmp:modern-cmake.pdf |Archive du 01/30/2020 le 04/03/2020}}
  
 ====Généralités de #pragma==== ====Généralités de #pragma====
Ligne 14: Ligne 28:
   * ''omp_get_num_threads()'' : nombre de threads dans la team.   * ''omp_get_num_threads()'' : nombre de threads dans la team.
  
-===#pragma omp for=== +====#pragma omp parallel====
-Doit être placé juste avant une boucle ''for''+
- +
-La boucle va être décomposée en n blocs (n étant le nombre de threads du CPU). L'ordre d'exécution des blocs est séquentiel car l'instruction ne précise pas l'exécution en parallèle. +
- +
-<code c> +
-#pragma omp for +
-for (int n 0; n < 10; ++n) +
-  printf(" %d", n); +
-</code> +
- +
-===#pragma omp parallel===+
 Doit être placé juste avant un bloc ''{}''. Doit être placé juste avant un bloc ''{}''.
  
Ligne 37: Ligne 40:
  
 Le code va s'exécute en parallèle. Avec cette seule instruction, la boucle for est exécutée 8*4000000000. Le code va s'exécute en parallèle. Avec cette seule instruction, la boucle for est exécutée 8*4000000000.
 +
 +====#pragma omp for====
 +===Généralités===
 +Doit être placé juste avant une boucle ''for''.
 +
 +La boucle va être décomposée en n blocs (n étant le nombre de threads du CPU). L'ordre d'exécution des blocs est séquentiel car l'instruction ne précise pas l'exécution en parallèle.
 +
 +<code c>
 +#pragma omp for
 +for (int n = 0; n < 10; ++n)
 +  printf(" %d", n);
 +</code>
  
 ===#pragma omp parallel for=== ===#pragma omp parallel for===
Ligne 60: Ligne 75:
 </code> </code>
  
-===#pragma omp simd=== 
-Cela parallélise les instructions de calcul pour utiliser au mieux les SSE* et autres. On peut autant y faire des calculs flottants qu'en entier. Le fonctionnement est similaire aux calculs CUDA : 
- 
-{{ :lang:cpp:openmp:simd-1.svg?600 |}} 
- 
-<code c> 
-#pragma omp simd 
-for(int n=0; n<size; ++n) 
-  sinTable[n] = std::sin(2 * M_PI * n / size); 
-</code> 
- 
-[[https://doc.itc.rwth-aachen.de/download/attachments/28344675/SIMD+Vectorization+with+OpenMP.PDF|SIMD Vectorization with OpenMP]] {{ :lang:cpp:openmp:simd_vectorization_with_openmp.pdf |Archive du 09/03/2019}} 
- 
-====Options==== 
 ===if=== ===if===
 ''#pragma'' est exécutée si la condition est vraie. ''#pragma'' est exécutée si la condition est vraie.
Ligne 138: Ligne 139:
  
   * ''monolithic'' / ''nonmonolithic''   * ''monolithic'' / ''nonmonolithic''
-monolithic : les blocs sont exécutés par ordre croissant, +    * ''monolithic'' : les blocs sont exécutés par ordre croissant, 
-nonmonolithic : les blocs sont exécutés par ordre non forcément croissant,+    * ''nonmonolithic'' : les blocs sont exécutés par ordre non forcément croissant,
  
 <code c> <code c>
 #pragma omp for schedule(nonmonotonic:dynamic) #pragma omp for schedule(nonmonotonic:dynamic)
-</c>+</code> 
 + 
 +  * Quelques tests 
 +Les cas 1, 2 et 3 présentent les résultats pour comparer la différence entre ''for'', ''parallel'' et ''parallel for''
 + 
 +Les cas 4, 5, 6 et 7 montrent la différence entre les différents types de ''schedule'' et l'impact de ''ordered'' 
 + 
 +<code> 
 +int main() 
 +
 +  #pragma … 
 +  for (int i = 0; i < 6; i++) 
 +  { 
 +    printf("%d %d\n", omp_get_thread_num(), i); 
 +  } 
 +
 +</code> 
 + 
 +    *1 ''#pragma omp for'' : la boucle ''for'' est exécutée dans la team (de 1 thread) car pas de ''parallel''
 +<code> 
 +0 0 
 +0 1 
 +0 2 
 +0 3 
 +0 4 
 +0 5 
 +</code> 
 + 
 +    *2 ''#pragma omp parallel num_threads(3)'' : la boucle ''for'' est bien dupliquée et exécutée en parallèle. 
 +<code> 
 +0 0 
 +0 1 
 +2 0 
 +2 1 
 +0 2 
 +0 3 
 +0 4 
 +0 5 
 +1 0 
 +1 1 
 +1 2 
 +1 3 
 +1 4 
 +1 5 
 +2 2 
 +2 3 
 +2 4 
 +2 5 
 +</code> 
 + 
 +    *3 ''#pragma omp parallel for num_threads(3)'' : la boucle ''for'' est exécutée en parallèle sans duplication. 
 +<code> 
 +0 0 
 +0 1 
 +2 4 
 +1 2 
 +1 3 
 +2 5 
 +</code> 
 + 
 +    *4 ''#pragma omp parallel for schedule(dynamic) num_threads(3)'' : la boucle ''for'' est exécutée en parallèle sans duplication. 
 +<code> 
 +1 2 
 +1 3 
 +1 4 
 +1 5 
 +0 0 
 +2 1 
 +</code> 
 +Ici, c'est le thread 0 qui fait tout. Cela peut s'expliquer par le délai très court pour exécuter chaque thread. Avec ''dynamic'', OpenMP a décidé de créer des tailles de blocs de 1. C'est le deuxième thread (n°1) qui s'exécute en premier et qui interroge OpenMP pour connaitre son bloc suivant. Il le fait 3 fois d'affilé et termine la boucle. C'est uniquement après que les autres threads s'exécutent. Avec une boucle plus grande (30 itérations), on a : 
 +<code> 
 +0 0 
 +0 3 
 +0 4 
 +0 5 
 +2 1 
 +2 7 
 +2 8 
 +2 9 
 +1 2 
 +1 11 
 +1 12 
 +0 6 
 +0 14 
 +1 13 
 +2 10 
 +0 15 
 +1 16 
 +1 19 
 +1 20 
 +1 21 
 +1 22 
 +1 23 
 +1 24 
 +1 25 
 +1 26 
 +1 27 
 +1 28 
 +1 29 
 +0 18 
 +2 17 
 +</code> 
 +On voit que au départ chaque thread exécute une itération de la boucle mais que le travail est réparti entre chaque thread de la team. 
 + 
 +    *5 ''#pragma omp parallel for schedule(nonmonotonic:dynamic) num_threads(3)'' : les blocs de la boucle ''for'' sont exécutée sans obligation de les lancer dans l'ordre. J'ai pas vraiment vu de réel différence avec cet exemple. 
 + 
 +    *6 ''#pragma omp parallel for schedule(static) num_threads(3)'' 
 +<code c> 
 +int main() 
 +
 +  #pragma omp parallel for ordered schedule(static) num_threads(3) 
 +  for (int i = 0; i < 6; i++) 
 +  { 
 +    printf("%d %d\n", omp_get_thread_num(), i); 
 +    #pragma omp ordered 
 +    {  
 +      printf("%d\n", i); 
 +    } 
 +  } 
 +
 +</code> 
 + 
 +Rendu : 
 + 
 +<code> 
 +0 0 
 +
 +1 2 
 +2 4 
 +0 1 
 +
 +
 +1 3 
 +
 +
 +2 5 
 +
 +</code> 
 + 
 +On voit que les threads n°1 et 2 sont dans l'attente du thread n°0 à partir de la ligne 4. Ici, le problème est d'utiliser ''schedule(static)''. L'utilisation de ''schedule(dynamic)'' va créer des boucles de taille 1 pour réduire ces attentes. 
 + 
 +    *7 ''#pragma omp parallel for schedule(dynamic) num_threads(3)'' 
 +<code> 
 +0 0 
 +
 +0 3 
 +2 2 
 +1 1 
 +
 +1 4 
 +
 +
 +
 +2 5 
 +
 +</code> 
 + 
 +Avec ''dynamic'', les threads attendent moins. 
 + 
 +Dans notre cas particulier, l'utilisation de ''dynamic'' est plus lente car le processeur va passer plus de temps à changer de thread que d'exécuter le contenu de la boucle. Il faut donc que chaque itération de la boucle ait une charge assez soutenue. 
 + 
 +===collapse=== 
 +Fusionne les boucles imbriquées. 
 +<code c> 
 +#pragma omp parallel for collapse(2) num_threads(3) 
 +for(int y=0; y<4; ++y) 
 +  for(int x=0; x<3; ++x) 
 +  { 
 +    printf("%d %d %d\n", omp_get_thread_num(), x, y); 
 +  } 
 +</code> 
 + 
 +<WRAP center round important 60%> 
 +Les bornes de la boucle intérieure ne doit pas dépendre de la variable de la boucle externe. 
 +</WRAP> 
 + 
 +<code> 
 +0 0 0 
 +0 1 0 
 +0 2 0 
 +0 0 1 
 +2 2 2 
 +2 0 3 
 +2 1 3 
 +2 2 3 
 +1 1 1 
 +1 2 1 
 +1 0 2 
 +1 1 2 
 +</code> 
 + 
 +===reduction=== 
 +Est utilisé si une variable commune est modifiée lors de la parallélisation. 
 + 
 +<code c> 
 +int main() 
 +
 +int sum(0); 
 +#pragma omp parallel for schedule(...) num_threads(3) reduction(+:sum) 
 +for(int x=0; x<100000000; ++x) 
 +
 +  sum += 1; 
 +
 +printf("%d\n", sum); 
 +
 +</code> 
 + 
 +^''schedule''^Variable        ^''reduction''^Résultat ^Temps   ^ 
 +|static      |int             |non          |50231932 |0m0,431s| 
 +|static      |int             |oui          |100000000|0m0,071s| 
 +|static      |std::atomic<int>|non          |100000000|0m1,886s| 
 +|dynamic     |int             |non          |51166934 |0m3,087s| 
 +|dynamic     |int             |oui          |100000000|0m1,890s| 
 +|dynamic     |std::atomic<int>|non          |100000000|0m3,812s| 
 + 
 +====#pragma omp sections==== 
 +Défini des blocs de code qui peuvent s'exécuter en parallèle dans la team. Il faut donc le coupler avec ''#pragma omp parallel''
 + 
 +Ci-dessous, les 3 blocs s'exécutent en parallèle. 
 + 
 +<code c> 
 +#pragma omp parallel sections 
 +
 +  { 
 +    Work1(); 
 +  } 
 +  #pragma omp section 
 +  { 
 +    Work2(); 
 +    Work3(); 
 +  } 
 +  #pragma omp section 
 +  { 
 +    Work4(); 
 +  } 
 +
 +</code> 
 + 
 +====#pragma omp simd==== 
 +[[https://doc.itc.rwth-aachen.de/download/attachments/28344675/SIMD+Vectorization+with+OpenMP.PDF|SIMD Vectorization with OpenMP]] {{ :lang:cpp:openmp:simd_vectorization_with_openmp.pdf |Archive du 09/03/2019}} 
 + 
 +Cela parallélise les instructions de calcul pour utiliser au mieux les SSE* et autres. On peut autant y faire des calculs flottants qu'en entier. Le fonctionnement est similaire aux calculs CUDA : 
 + 
 +{{ :lang:cpp:openmp:simd-1.svg |}} 
 + 
 +<code c> 
 +#pragma omp simd 
 +for(int n=0; n<size; ++n) 
 +  sinTable[n] = std::sin(2 * M_PI * n / size); 
 +</code> 
 + 
 +===aligned=== 
 +SSE2 a besoin que les variables soient alignées en multiple de 16 octets. On peut dire à OpenMP que les variables sont toujours correctement alignées. Mais dans le cas contraire, les calculs seront faux. 
 + 
 +On peut déclarer soit au niveau de la variable, soit au niveau de la fonction. 
 + 
 +<code c> 
 +#pragma omp declare simd aligned(a,b:16) 
 +void add_arrays(float *__restrict__ a, float *__restrict__ b) 
 +
 +  #pragma omp simd aligned(a,b:16) 
 +  for(int n=0; n<8; ++n) a[n] += b[n]; 
 +}  
 +</code> 
 + 
 +''%%__restrict__%%'' permet de dire que le contenu ne change pas et que personne ne pointe dessus sauf la variable utilisée. 
 + 
 +===safelen=== 
 +Limite le nombre de calculs en parallèle via ''simd''. Utile si deux tableaux se superposent. 
 + 
 +===simdlen=== 
 +Taille des blocs SIMD à calculer en même temps. 
 + 
 +===linear=== 
 +Incrémente pour chaque boucle une variable. 
 +<code cpp> 
 +#pragma omp simd linear(b:2) 
 +for(int n=0; n<8; ++n) array[n] = b; 
 +</code> 
 + 
 +Ne marche pas avec GCC 8. 
 + 
 +===uniform=== 
 +Indique d'une variable est une constante. 
 + 
 +====#pragma omp task==== 
 +Déclare des tâches qui seront exécutée dans un thread parallèle. 
 + 
 +On utilise ''taskwait'' pour attendre que les tâches soient terminées. 
 + 
 +====#pragma omp single==== 
 +Impose l'exécution d'un bloc par un seul thread. Ci-dessous, la boucle est exécutée une seule fois son contenu est exécuté en parallèle grâce à ''#pragma omp task''
 + 
 +<code c> 
 +<#pragma omp parallel 
 +
 +  #pragma omp single 
 +  { 
 +    #pragma omp task 
 +    { 
 +      printf("%d\n", omp_get_thread_num()); 
 +    } 
 +    #pragma omp task 
 +    { 
 +      printf("%d\n", omp_get_thread_num()); 
 +    } 
 +    #pragma omp task 
 +    { 
 +      printf("%d\n", omp_get_thread_num()); 
 +    } 
 +    #pragma omp taskwait 
 +    printf("Fin %d\n", omp_get_thread_num()); 
 +  } 
 +
 +</code> 
 + 
 +====Bugs / messages d'erreur==== 
 +===Charger dynamiquement une DLL qui est liée à OpenMP=== 
 + 
 +OpenMP gère ses threads comme Windows. Quand un thread a terminé ce qu'il avait à faire, il reste dans la ''pool'' et est stocké en vue d'une éventuelle réutilisation. 
 + 
 +Dans le cas de la DLL de OpenMP, si on décharge la DLL ayant chargé OpenMP et que le pool de threads n'est pas vide, il y a un crash. 
 + 
 +Solution : définir obligatoirement la variable d'environnement ''OMP_WAIT_POLICY'' à ''passive'' pour que la durée avant libération de la mémoire des threads après leur mort soit nulle. 
 + 
 +Selon le code source de gcc (''libgomp\env.c'') et la fonction ''parse_wait_policy''
 + 
 +^''OMP_WAIT_POLICY''^Durée de vie du pool^ 
 +|Non défini.        | 3 ms               | 
 +|''active''         | 5 minutes          | 
 +|''passive''        | 0 ms               | 
 + 
 +[[https://stackoverflow.com/questions/34439956/vc-crash-when-freeing-a-dll-built-with-openmp|VC++: crash when freeing a DLL built with openMP]] {{ :lang:cpp:openmp:visual_c_-_vc_crash_when_freeing_a_dll_built_with_openmp_-_stack_overflow_2019-10-16_16_27_25_.html |Archive du 23/12/2015 le 16/10/2019}}
lang/cpp/openmp.1552230645.txt.gz · Dernière modification : 2019/03/10 16:10 de root