Découverte d'OpenMP : Optimisez vos codes en parallèle, sans prise de tête
L'optimisation du calcul parallèle est devenue une compétence incontournable pour tout développeur ou ingénieur qui veut maximiser les performances de ses applications. OpenMP est l'un des outils les plus populaires pour paralléliser vos codes en C, C++ ou Fortran, en quelques lignes. Dans cet article, on va plonger dans OpenMP d'une manière ludique et accessible, avec des exemples concrets et du code que vous pourrez tester vous-même.
Sommaire :
1. Création et gestion des threads
2. Maîtriser les régions parallèles : `single` et `master`
3. Barrières et synchronisation
4. La différence entre `critical` et `atomic`
5. Debugging et analyse de la sortie attendue
6. Exercice de code parallèle et optimisé
7. Conclusion et exercices pratiques
`
1. Création et gestion des threads
Imaginez que vous devez embaucher trois assistants pour exécuter différentes tâches. En programmation parallèle, ces assistants sont des **threads**. Pour en créer plusieurs, OpenMP vous propose la directive magique `#pragma omp parallel` avec l'option `num_threads(P)`. P étant le nombre de threads.
Exemple de code :
#include <stdio.h>
#include <omp.h>
#pragma omp parallel num_threads(3)
{
int thid = omp_get_thread_num();
printf("Thread %d est en train d'exécuter du code en parallèle.\n", thid);
}
```
Ce petit bijou de code crée 3 threads qui s'exécutent en parallèle, chacun imprimant son identifiant (0, 1 ou 2). Essayez de le lancer sur votre machine pour voir ces assistants en action !
2. Maîtriser les régions parallèles : "single" et "master"
Deux directives très importantes dans OpenMP : `single` et `master`. Elles vous permettent de contrôler quel thread exécute une section de code.
single : Un seul thread (choisi aléatoirement) exécute la section.
master : Seul le thread maître (le premier thread, avec l'ID 0) exécute la section.
Exemple de code :
#pragma omp single
{
printf("Hello World from threadId=%d (single)\n", threadId);
}
#pragma omp master
{
printf("Hello World from threadId=%d (master)\n", threadId);
}
Expérimentez avec ces directives pour voir comment elles influencent l'ordre des threads qui s'exécutent.
3. Barrières et synchronisation
Quand vous avez plusieurs threads, ils ne travaillent pas toujours au même rythme. C’est là qu'intervient la **barrière**. Une barrière, c'est un checkpoint où chaque thread doit attendre les autres avant de continuer.
Barrière explicite en OpenMP :
#pragma omp barrier
Tous les threads doivent atteindre ce point avant que l'exécution ne continue. Pratique pour synchroniser le travail entre eux.
4. La différence entre "critical" et "atomic"
Parfois, deux threads essaient de modifier la même variable au même moment, ce qui peut causer des erreurs. Pour résoudre cela, OpenMP nous propose deux solutions :
critical : Permet qu'un seul thread à la fois accède à une section critique du code.
atomic : Plus léger, mais ne fonctionne que sur des opérations simples (comme une addition).
Exemple de code :
#pragma omp critical
{
sum += partial_sum;
}
Exemple de code :
#pragma omp atomic
{
sum += partial_sum;
}
Utilisez `critical` pour des sections complexes et `atomic` pour des opérations simples.