lang:cpp:openmp
Différences
Ci-dessous, les différences entre deux révisions de la page.
Prochaine révision | Révision précédente | ||
lang:cpp:openmp [2019/03/09 08:32] – Création root | lang:cpp:openmp [2022/09/05 23:05] (Version actuelle) – [linear] : fix format root | ||
---|---|---|---|
Ligne 1: | Ligne 1: | ||
- | ====Compilateur==== | + | ====Compilation==== |
+ | ===Compilateur=== | ||
Pour activer l' | Pour activer l' | ||
[[https:// | [[https:// | ||
- | [[https://bisqwit.iki.fi/story/howto/openmp/|Guide into OpenMP: Easy multithreading programming for C++]] {{ : | + | [[https://www.openmp.org/wp-content/uploads/openmp-examples-4.5.0.pdf|OpenMP |
- | ====Pragma==== | + | [[https:// |
- | ===#pragma omp for=== | + | |
- | Doit être placé juste avant une boucle '' | + | |
- | La boucle va être décomposée en n blocs (n étant le nombre de threads du CPU). L' | + | ===CMake=== |
- | < | + | < |
- | #pragma omp for | + | find_package(OpenMP) |
- | for (int n = 0; n < 10; ++n) | + | if(OpenMP_CXX_FOUND) |
- | | + | |
+ | endif() | ||
</ | </ | ||
- | ===#pragma omp parallel=== | + | [[https:// |
+ | |||
+ | ====Généralités de # | ||
+ | ===Définitions=== | ||
+ | * team : représente le groupe de threads utilisé par OpenMP. | ||
+ | |||
+ | ===Fonctions OpenMP=== | ||
+ | * '' | ||
+ | * '' | ||
+ | |||
+ | ====#pragma omp parallel==== | ||
Doit être placé juste avant un bloc '' | Doit être placé juste avant un bloc '' | ||
Ligne 30: | Ligne 40: | ||
Le code va s' | Le code va s' | ||
+ | |||
+ | ====#pragma omp for==== | ||
+ | ===Généralités=== | ||
+ | Doit être placé juste avant une boucle '' | ||
+ | |||
+ | La boucle va être décomposée en n blocs (n étant le nombre de threads du CPU). L' | ||
+ | |||
+ | <code c> | ||
+ | #pragma omp for | ||
+ | for (int n = 0; n < 10; ++n) | ||
+ | printf(" | ||
+ | </ | ||
===#pragma omp parallel for=== | ===#pragma omp parallel for=== | ||
Ligne 53: | Ligne 75: | ||
</ | </ | ||
- | ===#pragma omp simd=== | + | ===if=== |
+ | ''# | ||
+ | <code c> | ||
+ | extern int parallelism_enabled; | ||
+ | #pragma omp parallel for if(parallelism_enabled) | ||
+ | </ | ||
+ | |||
+ | ===num_threads(X)=== | ||
+ | Indique le nombre de threads de la team. | ||
+ | |||
+ | <code c> | ||
+ | #pragma omp parallel num_threads(3) | ||
+ | </ | ||
+ | |||
+ | ===Gestion des variables=== | ||
+ | La variable est locale ('' | ||
+ | <code c> | ||
+ | int n,m; | ||
+ | #pragma omp parallel private(n) | ||
+ | printf(" | ||
+ | printf(" | ||
+ | </ | ||
+ | L' | ||
+ | |||
+ | Rendu : | ||
+ | <code c> | ||
+ | 4 0x7f341ff72e14 | ||
+ | 0 0x7ffd31aadaf4 | ||
+ | 7 0x7f341e76fe14 | ||
+ | 3 0x7f3420773e14 | ||
+ | 1 0x7f3421775e14 | ||
+ | 6 0x7f341ef70e14 | ||
+ | 5 0x7f341f771e14 | ||
+ | 2 0x7f3420f74e14 | ||
+ | 0x7ffd31aadb44 | ||
+ | </ | ||
+ | |||
+ | On voit qu'un bloc de taille '' | ||
+ | |||
+ | ===Taille des blocs=== | ||
+ | * '' | ||
+ | Par défaut, tous les threads s' | ||
+ | <code c> | ||
+ | #pragma omp for schedule(static) | ||
+ | </ | ||
+ | |||
+ | * '' | ||
+ | Le thread interroge OpenMP pour savoir quelle plage de données il doit exécuter puis une fois terminée, il interroge OpenMP à nouveau. | ||
+ | |||
+ | Utile quand chaque élément dans la boucle n'a pas un temps d' | ||
+ | |||
+ | La taille de chaque bloc peut aussi être imposée. | ||
+ | |||
+ | <code c> | ||
+ | #pragma omp for schedule(dynamic, | ||
+ | </ | ||
+ | |||
+ | * '' | ||
+ | C'est un mix entre '' | ||
+ | |||
+ | * '' | ||
+ | C'est OpenMP qui décide à l' | ||
+ | |||
+ | * '' | ||
+ | * '' | ||
+ | * '' | ||
+ | |||
+ | <code c> | ||
+ | #pragma omp for schedule(nonmonotonic: | ||
+ | </ | ||
+ | |||
+ | * Quelques tests | ||
+ | Les cas 1, 2 et 3 présentent les résultats pour comparer la différence entre '' | ||
+ | |||
+ | Les cas 4, 5, 6 et 7 montrent la différence entre les différents types de '' | ||
+ | |||
+ | < | ||
+ | int main() | ||
+ | { | ||
+ | #pragma … | ||
+ | for (int i = 0; i < 6; i++) | ||
+ | { | ||
+ | printf(" | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | *1 ''# | ||
+ | < | ||
+ | 0 0 | ||
+ | 0 1 | ||
+ | 0 2 | ||
+ | 0 3 | ||
+ | 0 4 | ||
+ | 0 5 | ||
+ | </ | ||
+ | |||
+ | *2 ''# | ||
+ | < | ||
+ | 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 | ||
+ | </ | ||
+ | |||
+ | *3 ''# | ||
+ | < | ||
+ | 0 0 | ||
+ | 0 1 | ||
+ | 2 4 | ||
+ | 1 2 | ||
+ | 1 3 | ||
+ | 2 5 | ||
+ | </ | ||
+ | |||
+ | *4 ''# | ||
+ | < | ||
+ | 1 2 | ||
+ | 1 3 | ||
+ | 1 4 | ||
+ | 1 5 | ||
+ | 0 0 | ||
+ | 2 1 | ||
+ | </ | ||
+ | Ici, c'est le thread 0 qui fait tout. Cela peut s' | ||
+ | < | ||
+ | 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 | ||
+ | </ | ||
+ | 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 ''# | ||
+ | |||
+ | *6 ''# | ||
+ | <code c> | ||
+ | int main() | ||
+ | { | ||
+ | #pragma omp parallel for ordered schedule(static) num_threads(3) | ||
+ | for (int i = 0; i < 6; i++) | ||
+ | { | ||
+ | printf(" | ||
+ | #pragma omp ordered | ||
+ | { | ||
+ | printf(" | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Rendu : | ||
+ | |||
+ | < | ||
+ | 0 0 | ||
+ | 0 | ||
+ | 1 2 | ||
+ | 2 4 | ||
+ | 0 1 | ||
+ | 1 | ||
+ | 2 | ||
+ | 1 3 | ||
+ | 3 | ||
+ | 4 | ||
+ | 2 5 | ||
+ | 5 | ||
+ | </ | ||
+ | |||
+ | On voit que les threads n°1 et 2 sont dans l' | ||
+ | |||
+ | *7 ''# | ||
+ | < | ||
+ | 0 0 | ||
+ | 0 | ||
+ | 0 3 | ||
+ | 2 2 | ||
+ | 1 1 | ||
+ | 1 | ||
+ | 1 4 | ||
+ | 2 | ||
+ | 3 | ||
+ | 4 | ||
+ | 2 5 | ||
+ | 5 | ||
+ | </ | ||
+ | |||
+ | Avec '' | ||
+ | |||
+ | Dans notre cas particulier, | ||
+ | |||
+ | ===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(" | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | <WRAP center round important 60%> | ||
+ | Les bornes de la boucle intérieure ne doit pas dépendre de la variable de la boucle externe. | ||
+ | </ | ||
+ | |||
+ | < | ||
+ | 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 | ||
+ | </ | ||
+ | |||
+ | ===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(+: | ||
+ | for(int x=0; x< | ||
+ | { | ||
+ | sum += 1; | ||
+ | } | ||
+ | printf(" | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | ^'' | ||
+ | |static | ||
+ | |static | ||
+ | |static | ||
+ | |dynamic | ||
+ | |dynamic | ||
+ | |dynamic | ||
+ | |||
+ | ====#pragma omp sections==== | ||
+ | Défini des blocs de code qui peuvent s' | ||
+ | |||
+ | Ci-dessous, les 3 blocs s' | ||
+ | |||
+ | <code c> | ||
+ | #pragma omp parallel sections | ||
+ | { | ||
+ | { | ||
+ | Work1(); | ||
+ | } | ||
+ | #pragma omp section | ||
+ | { | ||
+ | Work2(); | ||
+ | Work3(); | ||
+ | } | ||
+ | #pragma omp section | ||
+ | { | ||
+ | Work4(); | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | ====#pragma omp simd==== | ||
+ | [[https:// | ||
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 : | 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 : | ||
- | {{ : | + | {{ : |
<code c> | <code c> | ||
Ligne 64: | Ligne 395: | ||
</ | </ | ||
- | [[https://doc.itc.rwth-aachen.de/download/attachments/28344675/SIMD+Vectorization+with+OpenMP.PDF|SIMD Vectorization with OpenMP]] {{ : | + | ===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, | ||
+ | void add_arrays(float *__restrict__ a, float *__restrict__ b) | ||
+ | { | ||
+ | #pragma omp simd aligned(a, | ||
+ | for(int n=0; n<8; ++n) a[n] += b[n]; | ||
+ | } | ||
+ | </code> | ||
+ | |||
+ | '' | ||
+ | |||
+ | ===safelen=== | ||
+ | Limite le nombre de calculs en parallèle via '' | ||
+ | |||
+ | ===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: | ||
+ | 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 '' | ||
+ | |||
+ | ====#pragma omp single==== | ||
+ | Impose l' | ||
+ | |||
+ | <code c> | ||
+ | <#pragma omp parallel | ||
+ | { | ||
+ | #pragma omp single | ||
+ | { | ||
+ | #pragma omp task | ||
+ | { | ||
+ | printf(" | ||
+ | } | ||
+ | #pragma omp task | ||
+ | { | ||
+ | printf(" | ||
+ | } | ||
+ | #pragma omp task | ||
+ | { | ||
+ | printf(" | ||
+ | } | ||
+ | #pragma omp taskwait | ||
+ | printf(" | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | ====Bugs / messages d' | ||
+ | ===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 '' | ||
+ | |||
+ | 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' | ||
+ | |||
+ | Selon le code source de gcc ('' | ||
+ | |||
+ | ^'' | ||
+ | |Non défini. | ||
+ | |'' | ||
+ | |'' | ||
+ | |||
+ | [[https://stackoverflow.com/questions/34439956/ |
lang/cpp/openmp.1552116747.txt.gz · Dernière modification : 2019/03/09 08:32 de root